summaryrefslogtreecommitdiff
path: root/addons/web/static
diff options
context:
space:
mode:
authorJiten (OpenERP) <jra@tinyerp.com>2012-11-19 11:11:38 +0530
committerJiten (OpenERP) <jra@tinyerp.com>2012-11-19 11:11:38 +0530
commit4346af9c96ddc0e88d2d55d47ee0c4e21865aa2d (patch)
tree24feb522a8d01a5babe5a5a6fde7cc1c77b7de25 /addons/web/static
parent3646b669516f78aa0a1be69ca8a241468cc9d98b (diff)
parent37209eb5e76a7b373a0f302e1f62be32fd2f37d2 (diff)
[MERGE] Merged trunk.
bzr revid: jra@tinyerp.com-20121119054138-lkrx5jo1tgbr71ek
Diffstat (limited to 'addons/web/static')
-rw-r--r--addons/web/static/lib/jquery.tipsy/jquery.tipsy.js3
-rw-r--r--addons/web/static/lib/jquery.ui.bootstrap/css/custom-theme/jquery-ui-1.9.0.custom.css10
-rw-r--r--addons/web/static/src/css/base.css208
-rw-r--r--addons/web/static/src/css/base.sass180
-rw-r--r--addons/web/static/src/js/boot.js10
-rw-r--r--addons/web/static/src/js/chrome.js92
-rw-r--r--addons/web/static/src/js/corelib.js292
-rw-r--r--addons/web/static/src/js/coresetup.js65
-rw-r--r--addons/web/static/src/js/data.js18
-rw-r--r--addons/web/static/src/js/formats.js36
-rw-r--r--addons/web/static/src/js/search.js136
-rw-r--r--addons/web/static/src/js/testing.js393
-rw-r--r--addons/web/static/src/js/view_form.js248
-rw-r--r--addons/web/static/src/js/view_list.js38
-rw-r--r--addons/web/static/src/js/view_list_editable.js100
-rw-r--r--addons/web/static/src/js/views.js70
-rw-r--r--addons/web/static/src/xml/base.xml89
-rw-r--r--addons/web/static/test/Widget.js158
-rw-r--r--addons/web/static/test/class.js67
-rw-r--r--addons/web/static/test/evals.js39
-rw-r--r--addons/web/static/test/form.js40
-rw-r--r--addons/web/static/test/formats.js203
-rw-r--r--addons/web/static/test/list-editable.js323
-rw-r--r--addons/web/static/test/list-utils.js223
-rw-r--r--addons/web/static/test/list.js56
-rw-r--r--addons/web/static/test/mutex.js60
-rw-r--r--addons/web/static/test/registry.js77
-rw-r--r--addons/web/static/test/rpc.js48
-rw-r--r--addons/web/static/test/search.js657
-rw-r--r--addons/web/static/test/test.html62
-rw-r--r--addons/web/static/test/testing.js331
31 files changed, 2562 insertions, 1770 deletions
diff --git a/addons/web/static/lib/jquery.tipsy/jquery.tipsy.js b/addons/web/static/lib/jquery.tipsy/jquery.tipsy.js
index 5929d591261..e978042f21b 100644
--- a/addons/web/static/lib/jquery.tipsy/jquery.tipsy.js
+++ b/addons/web/static/lib/jquery.tipsy/jquery.tipsy.js
@@ -27,7 +27,8 @@
var $tip = this.tip();
$tip.find('.tipsy-inner')[this.options.html ? 'html' : 'text'](title);
- $tip[0].className = 'tipsy openerp oe_tooltip '; // reset classname in case of dynamic gravity
+ $tip[0].className = 'tipsy '; // reset classname in case of dynamic gravity
+ $tip.openerpClass('oe_tooltip');
$tip.remove().css({top: 0, left: 0, visibility: 'hidden', display: 'block'}).prependTo(document.body);
var pos = $.extend({}, this.$element.offset(), {
diff --git a/addons/web/static/lib/jquery.ui.bootstrap/css/custom-theme/jquery-ui-1.9.0.custom.css b/addons/web/static/lib/jquery.ui.bootstrap/css/custom-theme/jquery-ui-1.9.0.custom.css
index a06e9447afc..f19c56caed1 100644
--- a/addons/web/static/lib/jquery.ui.bootstrap/css/custom-theme/jquery-ui-1.9.0.custom.css
+++ b/addons/web/static/lib/jquery.ui.bootstrap/css/custom-theme/jquery-ui-1.9.0.custom.css
@@ -1115,7 +1115,7 @@ body .ui-tooltip { border-width:2px; }
/*** Input field styling from Bootstrap **/
- input, textarea {
+/* input, textarea {
-webkit-transition: border linear 0.2s, box-shadow linear 0.2s;
-moz-transition: border linear 0.2s, box-shadow linear 0.2s;
-ms-transition: border linear 0.2s, box-shadow linear 0.2s;
@@ -1137,12 +1137,12 @@ input[type=file]:focus, input[type=checkbox]:focus, select:focus {
-moz-box-shadow: none;
box-shadow: none;
outline: 1px dotted #666;
-}
+}*/
-input[type="text"],
-input[type="password"],
+/*input[type="text"],
+input[type="password"],*/
.ui-autocomplete-input,
-textarea,
+/*textarea,*/
.uneditable-input {
display: inline-block;
padding: 4px;
diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css
index 94d02758ad0..ab7b74f85fe 100644
--- a/addons/web/static/src/css/base.css
+++ b/addons/web/static/src/css/base.css
@@ -20,20 +20,6 @@
font-style: normal;
}
-@media print {
- .oe_topbar, .oe_leftbar, .oe_loading {
- display: none !important;
- }
-}
-.openerp.openerp_webclient_container {
- height: 100%;
-}
-
-.text-tag .text-button {
- height: auto !important;
- min-height: 16px;
-}
-
.openerp {
padding: 0;
margin: 0;
@@ -46,6 +32,9 @@
* http://stackoverflow.com/questions/2855589/replace-input-type-file-by-an-image
*/
}
+.openerp.openerp_webclient_container {
+ height: 100%;
+}
.openerp :-moz-placeholder {
color: #afafb6 !important;
font-style: italic !important;
@@ -197,6 +186,10 @@
.openerp .oe_bounce_container {
display: inline-block;
}
+.openerp .text-tag .text-button {
+ height: auto !important;
+ min-height: 16px;
+}
.openerp .ui-tabs {
position: static;
}
@@ -1136,6 +1129,7 @@
height: 40px;
width: 157px;
margin: 14px 0;
+ border: 0;
}
.openerp .oe_footer {
position: fixed;
@@ -2306,16 +2300,16 @@
text-align: justify;
}
.openerp .oe_form_editable .oe_form .oe_form_field_integer input {
- width: 6em !important;
+ width: 6em;
}
.openerp .oe_form_editable .oe_form .oe_form_field_float input {
- width: 7em !important;
+ width: 7em;
}
.openerp .oe_form_editable .oe_form .oe_form_field_date input {
- width: 7.5em !important;
+ width: 7.5em;
}
.openerp .oe_form_editable .oe_form .oe_form_field_datetime input {
- width: 11.5em !important;
+ width: 11.5em;
}
.openerp .oe_hidden_input_file {
position: relative;
@@ -2361,6 +2355,77 @@
.openerp .oe_form .oe_form_field_image:hover .oe_form_field_image_controls {
display: block;
}
+.openerp .oe_fileupload {
+ display: inline-block;
+ clear: both;
+ width: 100%;
+}
+.openerp .oe_fileupload .oe_add {
+ float: left;
+ position: relative;
+ width: 100%;
+ left: 2px;
+ top: 7px;
+}
+.openerp .oe_fileupload .oe_add button {
+ display: inline;
+ height: 24px;
+ font-size: 12px;
+ line-height: 12px;
+ vertical-align: middle;
+}
+.openerp .oe_fileupload .oe_add button.oe_attach {
+ width: 24px;
+ overflow: hidden;
+ width: 24px;
+ overflow: hidden;
+ background: transparent;
+ color: #7c7bad;
+ box-shadow: none;
+ border: none;
+ text-shadow: none;
+}
+.openerp .oe_fileupload .oe_add button.oe_attach .oe_e {
+ position: relative;
+ top: -1px;
+ left: -9px;
+}
+.openerp .oe_fileupload .oe_add input.oe_form_binary_file {
+ display: inline-block;
+ margin-left: -5px;
+ height: 28px;
+ width: 52px;
+ margin-top: -26px;
+}
+.openerp .oe_fileupload .oe_add .oe_attach_label {
+ color: #7c7bad;
+ margin-left: -3px;
+}
+.openerp .oe_fileupload .oe_attachments {
+ margin-bottom: 4px;
+ margin-right: 0px;
+ font-size: 12px;
+ border-radius: 2px;
+ border: solid 1px rgba(124, 123, 173, 0.14);
+}
+.openerp .oe_fileupload .oe_attachments .oe_attachment {
+ padding: 2px;
+ padding-left: 4px;
+ padding-right: 4px;
+}
+.openerp .oe_fileupload .oe_attachments .oe_attachment .oe_e {
+ font-size: 23px;
+ margin-top: -5px;
+}
+.openerp .oe_fileupload .oe_attachments .oe_attachment .oe_e:hover {
+ text-decoration: none;
+}
+.openerp .oe_fileupload .oe_attachments .oe_attachment:nth-child(odd) {
+ background: white;
+}
+.openerp .oe_fileupload .oe_attachments .oe_attachment:nth-child(even) {
+ background: #f4f5fa;
+}
.openerp .oe_form_field_many2one td:first-child {
position: relative;
}
@@ -2374,6 +2439,9 @@
float: right;
padding-left: 2px;
}
+.openerp .oe_form_field_many2one input {
+ padding-right: 13px;
+}
.openerp.ui-autocomplete li.oe_m2o_dropdown_option a {
font-style: italic;
padding-left: 2em;
@@ -2578,32 +2646,33 @@
top: 5px;
}
.openerp .oe_list.oe_list_editable.oe_editing .oe_m2o_cm_button {
- display: none;
+ line-height: 19px;
}
-.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field input, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field textarea {
- height: 27px;
+.openerp .oe_list.oe_list_editable.oe_editing .oe_input_icon {
+ margin-top: 5px;
+}
+.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field {
+ min-width: 0;
+ max-width: none;
}
.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field input, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field textarea {
+ height: 27px;
-moz-border-radius: 0;
-webkit-border-radius: 0;
border-radius: 0;
border: 1px solid #aaaaff;
margin: 0;
}
+.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field input, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field textarea, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field select {
+ min-width: 0;
+}
.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_float input, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_view_integer input {
text-align: right;
width: 100% !important;
}
-.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_datetime > span, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_date > span {
- width: 100% !important;
-}
.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_datetime input.oe_datepicker_master, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_date input.oe_datepicker_master {
width: 100% !important;
}
-.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field .oe_form_field_float, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field .oe_form_view_integer, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_datetime, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_date {
- min-width: 0 !important;
- max-width: none !important;
-}
.openerp .oe_list_group_name {
white-space: nowrap;
}
@@ -2668,6 +2737,9 @@
text-align: right !important;
max-width: 100px;
}
+.openerp .oe_list_content td.oe_list_field_date, .openerp .oe_list_content th.oe_list_header_date {
+ min-width: 6em;
+}
.openerp .oe_list_content > thead {
border-bottom: 2px solid #cacaca;
background: #eeeeee;
@@ -2735,6 +2807,10 @@
.openerp .oe_list_content > tbody > tr > td.oe_list_checkbox:first-child:after, .openerp .oe_list_content > tbody > tr th.oe_list_checkbox:first-child:after {
border-width: 0;
}
+.openerp .oe_list_content > tbody > tr > td.oe_list_field_boolean input {
+ filter: alpha(opacity=50);
+ opacity: 0.5;
+}
.openerp .oe_list_content > tbody > tr:nth-child(odd) {
background-color: #f0f0fa;
background-color: #f0f0fa;
@@ -2882,78 +2958,6 @@
color: #333333;
}
-.openerp .oe_fileupload {
- display: inline-block;
- clear: both;
- width: 100%;
-}
-.openerp .oe_fileupload .oe_add {
- float: left;
- position: relative;
- width: 100%;
- left: 2px;
- top: 7px;
-}
-.openerp .oe_fileupload .oe_add button {
- display: inline;
- height: 24px;
- font-size: 12px;
- line-height: 12px;
- vertical-align: middle;
-}
-.openerp .oe_fileupload .oe_add button.oe_attach {
- width: 24px;
- overflow: hidden;
- width: 24px;
- overflow: hidden;
- background: transparent;
- color: #7c7bad;
- box-shadow: none;
- border: none;
- text-shadow: none;
-}
-.openerp .oe_fileupload .oe_add button.oe_attach .oe_e {
- position: relative;
- top: -1px;
- left: -9px;
-}
-.openerp .oe_fileupload .oe_add input.oe_form_binary_file {
- display: inline-block;
- margin-left: -5px;
- height: 28px;
- width: 52px;
- margin-top: -26px;
-}
-.openerp .oe_fileupload .oe_add .oe_attach_label {
- color: #7c7bad;
- margin-left: -3px;
-}
-.openerp .oe_fileupload .oe_attachments {
- margin-bottom: 4px;
- margin-right: 0px;
- font-size: 12px;
- border-radius: 2px;
- border: solid 1px rgba(124, 123, 173, 0.14);
-}
-.openerp .oe_fileupload .oe_attachments .oe_attachment {
- padding: 2px;
- padding-left: 4px;
- padding-right: 4px;
-}
-.openerp .oe_fileupload .oe_attachments .oe_attachment .oe_e {
- font-size: 23px;
- margin-top: -5px;
-}
-.openerp .oe_fileupload .oe_attachments .oe_attachment .oe_e:hover {
- text-decoration: none;
-}
-.openerp .oe_fileupload .oe_attachments .oe_attachment:nth-child(odd) {
- background: white;
-}
-.openerp .oe_fileupload .oe_attachments .oe_attachment:nth-child(even) {
- background: #f4f5fa;
-}
-
.kitten-mode-activated {
background-image: url(http://placekitten.com/g/1365/769);
background-size: cover;
@@ -3012,8 +3016,8 @@ div.ui-widget-overlay {
.openerp {
text-shadow: none;
}
- .openerp .oe_header_row, .openerp ul.oe_header, .openerp div.oe_mail_thread_action, .openerp .oe_mail_recthread_actions, .openerp .oe_button_box, .openerp .oe_form button, .openerp button.oe_invite, .openerp .oe_form header, .openerp .openerp .oe_notebook > li.ui-state-default {
- display: none;
+ .openerp .oe_header_row, .openerp ul.oe_header, .openerp div.oe_mail_thread_action, .openerp .oe_mail_recthread_actions, .openerp .oe_button_box, .openerp .oe_form button, .openerp button.oe_invite, .openerp .oe_form header, .openerp .openerp .oe_notebook > li.ui-state-default, .openerp .oe_topbar, .openerp .oe_leftbar, .openerp .oe_loading {
+ display: none !important;
}
.openerp .oe_list_content button, .openerp .oe_list_content input[type=checkbox] {
visibility: hidden;
diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass
index f911929e159..c5fcb8008a5 100644
--- a/addons/web/static/src/css/base.sass
+++ b/addons/web/static/src/css/base.sass
@@ -140,18 +140,6 @@ $sheet-padding: 16px
// }}}
-@media print
- .oe_topbar, .oe_leftbar, .oe_loading
- display: none !important
-
-.openerp.openerp_webclient_container
- height: 100%
-
-// jQueryUI css bug fixing
-.text-tag .text-button
- height: auto !important
- min-height: 16px
-
.openerp
// Global style {{{
padding: 0
@@ -161,6 +149,8 @@ $sheet-padding: 16px
font-size: 13px
background: white
text-shadow: 0 1px 1px rgba(255, 255, 255, 0.5)
+ &.openerp_webclient_container
+ height: 100%
// }}}
//Placeholder style{{{
\:-moz-placeholder
@@ -254,6 +244,11 @@ $sheet-padding: 16px
.oe_bounce_container
display: inline-block
+ // Bug lp:1051746
+ .text-tag .text-button
+ height: auto !important
+ min-height: 16px
+
// bug noted in jquery ui CSS doesn't seem to occur in IE9,
// so remove position:relative
.ui-tabs
@@ -928,6 +923,7 @@ $sheet-padding: 16px
height: 40px
width: 157px
margin: 14px 0
+ border: 0
.oe_footer
position: fixed
bottom: 0
@@ -1837,13 +1833,13 @@ $sheet-padding: 16px
.oe_form_editable
.oe_form
.oe_form_field_integer input
- width: 6em !important
+ width: 6em
.oe_form_field_float input
- width: 7em !important
+ width: 7em
.oe_form_field_date input
- width: 7.5em !important
+ width: 7.5em
.oe_form_field_datetime input
- width: 11.5em !important
+ width: 11.5em
// }}}
// FormView.fields_binary {{{
/* http://www.quirksmode.org/dom/inputfile.html
@@ -1886,6 +1882,64 @@ $sheet-padding: 16px
@include box-sizing(border)
&:hover .oe_form_field_image_controls
display: block
+ .oe_fileupload
+ display: inline-block
+ clear: both
+ width: 100%
+ .oe_add
+ float: left
+ position: relative
+ width: 100%
+ left: +2px
+ top: +7px
+ button
+ display: inline
+ height: 24px
+ font-size: 12px
+ line-height: 12px
+ vertical-align: middle
+ button.oe_attach
+ width: 24px
+ overflow: hidden
+ width: 24px
+ overflow: hidden
+ background: transparent
+ color: #7C7BAD
+ box-shadow: none
+ border: none
+ text-shadow: none
+ .oe_e
+ position: relative
+ top: -1px
+ left: -9px
+ input.oe_form_binary_file
+ display: inline-block
+ margin-left: -5px
+ height: 28px
+ width: 52px
+ margin-top: -26px
+ .oe_attach_label
+ color: #7C7BAD
+ margin-left: -3px
+ .oe_attachments
+ margin-bottom: 4px
+ margin-right: 0px
+ font-size: 12px
+ border-radius: 2px
+ border: solid 1px rgba(124,123,173,0.14)
+ .oe_attachment
+ padding: 2px
+ padding-left: 4px
+ padding-right: 4px
+ .oe_e
+ font-size: 23px
+ margin-top: -5px
+ .oe_e:hover
+ text-decoration: none
+ .oe_attachment:nth-child(odd)
+ background: white
+ .oe_attachment:nth-child(even)
+ background: #F4F5FA
// }}}
// FormView.many2one {{{
.oe_form_field_many2one
@@ -1899,6 +1953,8 @@ $sheet-padding: 16px
line-height: 14px
float: right
padding-left: 2px
+ input
+ padding-right: 13px
&.ui-autocomplete
li.oe_m2o_dropdown_option a
font-style: italic
@@ -2041,26 +2097,26 @@ $sheet-padding: 16px
.oe_m2o_drop_down_button
top: 5px
.oe_m2o_cm_button
- display: none
+ line-height: 19px
+ .oe_input_icon
+ margin-top: 5px
.oe_form_field
+ min-width: 0
+ max-width: none
input, textarea
height: $row-height
- input, textarea
@include radius(0)
border: 1px solid #aaf
margin: 0
+ input, textarea, select
+ min-width: 0
&.oe_form_field_float,&.oe_form_view_integer
input
text-align: right
width: 100% !important
&.oe_form_field_datetime,&.oe_form_field_date
- > span
- width: 100% !important
input.oe_datepicker_master
width: 100% !important
- .oe_form_field_float,.oe_form_view_integer,&.oe_form_field_datetime,&.oe_form_field_date
- min-width: 0 !important
- max-width: none !important
.oe_list_group_name
white-space: nowrap
// }}}
@@ -2119,6 +2175,8 @@ $sheet-padding: 16px
td.oe_number
text-align: right !important
max-width: 100px
+ td.oe_list_field_date, th.oe_list_header_date
+ min-width: 6em
> thead
border-bottom: 2px solid #cacaca
background: #eee
@@ -2168,6 +2226,8 @@ $sheet-padding: 16px
width: 17px
&:after
border-width: 0
+ > td.oe_list_field_boolean input
+ @include opacity()
> tr:nth-child(odd)
background-color: #f0f0fa
@include vertical-gradient(#f0f0fa, #eeeef6)
@@ -2282,67 +2342,6 @@ $sheet-padding: 16px
float: right
color: #333
// }}}
-
-.openerp
- .oe_fileupload
- display: inline-block
- clear: both
- width: 100%
- .oe_add
- float: left
- position: relative
- width: 100%
- left: +2px
- top: +7px
- button
- display: inline
- height: 24px
- font-size: 12px
- line-height: 12px
- vertical-align: middle
- button.oe_attach
- width: 24px
- overflow: hidden
- width: 24px
- overflow: hidden
- background: transparent
- color: #7C7BAD
- box-shadow: none
- border: none
- text-shadow: none
- .oe_e
- position: relative
- top: -1px
- left: -9px
- input.oe_form_binary_file
- display: inline-block
- margin-left: -5px
- height: 28px
- width: 52px
- margin-top: -26px
- .oe_attach_label
- color: #7C7BAD
- margin-left: -3px
- .oe_attachments
- margin-bottom: 4px
- margin-right: 0px
- font-size: 12px
- border-radius: 2px
- border: solid 1px rgba(124,123,173,0.14)
- .oe_attachment
- padding: 2px
- padding-left: 4px
- padding-right: 4px
- .oe_e
- font-size: 23px
- margin-top: -5px
- .oe_e:hover
- text-decoration: none
- .oe_attachment:nth-child(odd)
- background: white
- .oe_attachment:nth-child(even)
- background: #F4F5FA
-
// Kitten Mode {{{
.kitten-mode-activated
background-image: url(http://placekitten.com/g/1365/769)
@@ -2352,11 +2351,13 @@ $sheet-padding: 16px
opacity: 0.70
// }}}
+// jQueryUI top level {{{
// The jQuery-ui overlay and Autocomplete are outside the .openerp div, please don't add indentation !!!
div.ui-widget-overlay
background: black
@include opacity(0.3)
-
+// TODO: I think only the overlay is problematic, the other top level widgets should use $.fn.openerpClass()
+// eg: $el.autocomplete().openerpClass();
.ui-widget
font-family: "Lucida Grande", Helvetica, Verdana, Arial, sans-serif
color: #4c4c4c
@@ -2383,11 +2384,14 @@ div.ui-widget-overlay
.ui-corner-all
@include radius(3px)
+// }}}
+// @media print {{{
@media print
.openerp
- .oe_header_row, ul.oe_header, div.oe_mail_thread_action, .oe_mail_recthread_actions, .oe_button_box, .oe_form button, button.oe_invite, .oe_form header, .openerp .oe_notebook > li.ui-state-default
- display: none
+ .oe_header_row, ul.oe_header, div.oe_mail_thread_action, .oe_mail_recthread_actions, .oe_button_box, .oe_form button, button.oe_invite, .oe_form header, .openerp .oe_notebook > li.ui-state-default, .oe_topbar, .oe_leftbar, .oe_loading
+ // We use !important here because jQuery adds @style = display: block on elements when using $.fn.show()
+ display: none !important
.oe_list_content
button, input[type=checkbox]
visibility: hidden
@@ -2413,5 +2417,7 @@ div.ui-widget-overlay
background: none
.openerp div.oe_mail_wall
overflow: hidden !important
+// }}}
+
// au BufWritePost,FileWritePost *.sass :!sass --style expanded --line-numbers <afile> > "%:p:r.css"
// vim:tabstop=4:shiftwidth=4:softtabstop=4:fdm=marker:
diff --git a/addons/web/static/src/js/boot.js b/addons/web/static/src/js/boot.js
index 05ce709e80a..631a7f8c6f5 100644
--- a/addons/web/static/src/js/boot.js
+++ b/addons/web/static/src/js/boot.js
@@ -19,12 +19,14 @@
/**
* OpenERP instance constructor
*
- * @param {Array} modules list of modules to initialize
+ * @param {Array|String} modules list of modules to initialize
*/
init: function(modules) {
- // By default only web will be loaded, the rest will be by loaded
- // by openerp.web.Session on the first session_authenticate
- modules = _.union(['web'], modules || []);
+ if (modules === "fuck your shit, don't load anything you cunt") {
+ modules = [];
+ } else {
+ modules = _.union(['web'], modules || []);
+ }
var new_instance = {
// links to the global openerp
_openerp: openerp,
diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js
index 9c249a5fb0c..b95550c74a0 100644
--- a/addons/web/static/src/js/chrome.js
+++ b/addons/web/static/src/js/chrome.js
@@ -48,7 +48,7 @@ instance.web.Notification = instance.web.Widget.extend({
*/
instance.web.dialog = function(element) {
var result = element.dialog.apply(element, _.rest(_.toArray(arguments)));
- result.dialog("widget").addClass("openerp");
+ result.dialog("widget").openerpClass();
return result;
};
@@ -189,8 +189,15 @@ instance.web.Dialog = instance.web.Widget.extend({
}
});
-instance.web.CrashManager = instance.web.CallbackEnabled.extend({
+instance.web.CrashManager = instance.web.Class.extend({
+ init: function() {
+ this.active = true;
+ },
+
rpc_error: function(error) {
+ if (!this.active) {
+ return;
+ }
if (error.data.fault_code) {
var split = ("" + error.data.fault_code).split('\n')[0].split(' -- ');
if (split.length > 1) {
@@ -205,6 +212,9 @@ instance.web.CrashManager = instance.web.CallbackEnabled.extend({
}
},
show_warning: function(error) {
+ if (!this.active) {
+ return;
+ }
instance.web.dialog($('<div>' + QWeb.render('CrashManager.warning', {error: error}) + '</div>'), {
title: "OpenERP " + _.str.capitalize(error.type),
buttons: [
@@ -213,7 +223,9 @@ instance.web.CrashManager = instance.web.CallbackEnabled.extend({
});
},
show_error: function(error) {
- var self = this;
+ if (!this.active) {
+ return;
+ }
var buttons = {};
buttons[_t("Ok")] = function() {
$(this).dialog("close");
@@ -303,14 +315,14 @@ instance.web.DatabaseManager = instance.web.Widget.extend({
$('.oe_secondary_menus_container,.oe_user_menu_placeholder').empty();
var fetch_db = this.rpc("/web/database/get_list", {}).then(
function(result) {
- self.db_list = result.db_list;
+ self.db_list = result;
},
function (_, ev) {
ev.preventDefault();
self.db_list = null;
});
var fetch_langs = this.rpc("/web/session/get_lang_list", {}).done(function(result) {
- self.lang_list = result.lang_list;
+ self.lang_list = result;
});
return $.when(fetch_db, fetch_langs).done(self.do_render);
},
@@ -333,6 +345,7 @@ instance.web.DatabaseManager = instance.web.Widget.extend({
self.$el.find("tr td:first-child").addClass("oe_form_group_cell_label");
self.$el.find("label").addClass("oe_form_label");
self.$el.find("form[name=create_db_form]").validate({ submitHandler: self.do_create });
+ self.$el.find("form[name=duplicate_db_form]").validate({ submitHandler: self.do_duplicate });
self.$el.find("form[name=drop_db_form]").validate({ submitHandler: self.do_drop });
self.$el.find("form[name=backup_db_form]").validate({ submitHandler: self.do_backup });
self.$el.find("form[name=restore_db_form]").validate({ submitHandler: self.do_restore });
@@ -418,6 +431,18 @@ instance.web.DatabaseManager = instance.web.Widget.extend({
self.do_action(client_action);
});
},
+ do_duplicate: function(form) {
+ var self = this;
+ var fields = $(form).serializeArray();
+ self.rpc("/web/database/duplicate", {'fields': fields}).then(function(result) {
+ if (result.error) {
+ self.display_error(result);
+ return;
+ }
+ self.do_notify("Duplicating database", "The database has been duplicated.");
+ self.start();
+ });
+ },
do_drop: function(form) {
var self = this;
var $form = $(form),
@@ -518,6 +543,9 @@ instance.web.Login = instance.web.Widget.extend({
this.selected_db = null;
this.selected_login = null;
this.params = action.params || {};
+ if (_.isEmpty(this.params)) {
+ this.params = $.bbq.getState(true);
+ }
if (this.params.login_successful) {
this.on('login_successful', this, this.params.login_successful);
@@ -548,7 +576,7 @@ instance.web.Login = instance.web.Widget.extend({
return d;
},
on_db_loaded: function (result) {
- this.db_list = result.db_list;
+ this.db_list = result;
this.$("[name=db]").replaceWith(QWeb.render('Login.dblist', { db_list: this.db_list, selected_db: this.selected_db}));
if(this.db_list.length === 0) {
this.do_action("database_manager");
@@ -620,8 +648,34 @@ instance.web.Login = instance.web.Widget.extend({
instance.web.client_actions.add("login", "instance.web.Login");
/**
+ * Redirect to url by replacing window.location
+ * If wait is true, sleep 1s and wait for the server i.e. after a restart.
+ */
+instance.web.redirect = function(url, wait) {
+ // Dont display a dialog if some xmlhttprequest are in progress
+ if (instance.client && instance.client.crashmanager) {
+ instance.client.crashmanager.active = false;
+ }
+
+ var wait_server = function() {
+ instance.session.rpc("/web/webclient/version_info", {}).done(function() {
+ window.location = url;
+ }).fail(function() {
+ setTimeout(wait_server, 250);
+ });
+ };
+
+ if (wait) {
+ setTimeout(wait_server, 1000);
+ } else {
+ window.location = url;
+ }
+};
+
+/**
* Client action to reload the whole interface.
- * If params has an entry 'menu_id', it opens the given menu entry.
+ * If params.menu_id, it opens the given menu entry.
+ * If params.wait, reload will wait the openerp server to be reachable before reloading
*/
instance.web.Reload = function(parent, action) {
var params = action.params || {};
@@ -637,8 +691,8 @@ instance.web.Reload = function(parent, action) {
hash = "#menu_id=" + menu_id;
}
var url = l.protocol + "//" + l.host + l.pathname + search + hash;
- window.onerror = function() {};
- window.location = url;
+
+ instance.web.redirect(url, params.wait);
};
instance.web.client_actions.add("reload", "instance.web.Reload");
@@ -648,7 +702,7 @@ instance.web.client_actions.add("reload", "instance.web.Reload");
*/
instance.web.HistoryBack = function(parent) {
if (!parent.history_back()) {
- window.location = '/' + (window.location.search || '');
+ instance.web.Home(parent);
}
};
instance.web.client_actions.add("history_back", "instance.web.HistoryBack");
@@ -656,11 +710,10 @@ instance.web.client_actions.add("history_back", "instance.web.HistoryBack");
/**
* Client action to go back home.
*/
-instance.web.Home = instance.web.Widget.extend({
- init: function(parent) {
- window.location = '/' + (window.location.search || '');
- }
-});
+instance.web.Home = function(parent, action) {
+ var url = '/' + (window.location.search || '');
+ instance.web.redirect(url, action.params && action.params.wait);
+};
instance.web.client_actions.add("home", "instance.web.Home");
instance.web.ChangePassword = instance.web.Widget.extend({
@@ -887,7 +940,7 @@ instance.web.UserMenu = instance.web.Widget.extend({
if(res.company_id[0] > 1)
topbar_name = _.str.sprintf("%s (%s)", topbar_name, res.company_id[1]);
self.$el.find('.oe_topbar_name').text(topbar_name);
- var avatar_src = _.str.sprintf('%s/web/binary/image?session_id=%s&model=res.users&field=image_small&id=%s', self.session.prefix, self.session.session_id, self.session.uid);
+ var avatar_src = self.session.url('/web/binary/image', {model:'res.users', field: 'image_small', id: self.session.uid});
$avatar.attr('src', avatar_src);
});
};
@@ -899,7 +952,7 @@ instance.web.UserMenu = instance.web.Widget.extend({
on_menu_settings: function() {
var self = this;
if (!this.getParent().has_uncommitted_changes()) {
- self.rpc("/web/action/load", { action_id: "base.action_res_users_my" }, function(result) {
+ self.rpc("/web/action/load", { action_id: "base.action_res_users_my" }).done(function(result) {
result.res_id = instance.session.uid;
self.getParent().action_manager.do_action(result);
});
@@ -930,6 +983,7 @@ instance.web.Client = instance.web.Widget.extend({
return instance.session.session_bind(this.origin).then(function() {
var $e = $(QWeb.render(self._template, {}));
self.replaceElement($e);
+ $e.openerpClass();
self.bind_events();
return self.show_common();
});
@@ -1106,10 +1160,10 @@ instance.web.WebClient = instance.web.Client.extend({
var self = this;
var state = event.getState(true);
if (!_.isEqual(this._current_state, state)) {
- if(state.action_id === undefined && state.menu_id) {
+ if(!state.action && state.menu_id) {
self.menu.has_been_loaded.done(function() {
self.menu.do_reload().done(function() {
- self.menu.menu_click(state.menu_id)
+ self.menu.menu_click(state.menu_id);
});
});
} else {
diff --git a/addons/web/static/src/js/corelib.js b/addons/web/static/src/js/corelib.js
index d2e94d37939..06b3940f1e1 100644
--- a/addons/web/static/src/js/corelib.js
+++ b/addons/web/static/src/js/corelib.js
@@ -243,15 +243,17 @@ instance.web.ParentedMixin = {
/**
* Backbone's events. Do not ever use it directly, use EventDispatcherMixin instead.
+ *
+ * This class just handle the dispatching of events, it is not meant to be extended,
+ * nor used directly. All integration with parenting and automatic unregistration of
+ * events is done in EventDispatcherMixin.
+ *
+ * Copyright notice for the following Class:
*
* (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
* Backbone may be freely distributed under the MIT license.
* For all details and documentation:
* http://backbonejs.org
- *
- * This class just handle the dispatching of events, it is not meant to be extended,
- * nor used directly. All integration with parenting and automatic unregistration of
- * events is done in EventDispatcherMixin.
*
*/
var Events = instance.web.Class.extend({
@@ -334,7 +336,6 @@ var Events = instance.web.Class.extend({
return this;
}
});
-// end of Jeremy Ashkenas' code
instance.web.EventDispatcherMixin = _.extend({}, instance.web.ParentedMixin, {
__eventDispatcherMixin: true,
@@ -426,66 +427,94 @@ instance.web.PropertiesMixin = _.extend({}, instance.web.EventDispatcherMixin, {
}
});
-instance.web.CallbackEnabledMixin = _.extend({}, instance.web.PropertiesMixin, {
- init: function() {
- instance.web.PropertiesMixin.init.call(this);
- var self = this;
- // Transform on_/do_* methods into callbacks
- var callback_maker = function(fn) {
- return function() {
- return fn.apply(self, arguments);
- }
- };
- for (var name in this) {
- if(typeof(this[name]) == "function") {
- if((/^on_|^do_/).test(name)) {
- this[name] = callback_maker(this[name]);
- }
- }
- }
- },
+// Classes
+
+/**
+ * Base class for all visual components. Provides a lot of functionalities helpful
+ * for the management of a part of the DOM.
+ *
+ * Widget handles:
+ * - Rendering with QWeb.
+ * - Life-cycle management and parenting (when a parent is destroyed, all its children are
+ * destroyed too).
+ * - Insertion in DOM.
+ *
+ * Guide to create implementations of the Widget class:
+ * ==============================================
+ *
+ * Here is a sample child class:
+ *
+ * MyWidget = instance.base.Widget.extend({
+ * // the name of the QWeb template to use for rendering
+ * template: "MyQWebTemplate",
+ *
+ * init: function(parent) {
+ * this._super(parent);
+ * // stuff that you want to init before the rendering
+ * },
+ * start: function() {
+ * // stuff you want to make after the rendering, `this.$el` holds a correct value
+ * this.$el.find(".my_button").click(/* an example of event binding * /);
+ *
+ * // if you have some asynchronous operations, it's a good idea to return
+ * // a promise in start()
+ * var promise = this.rpc(...);
+ * return promise;
+ * }
+ * });
+ *
+ * Now this class can simply be used with the following syntax:
+ *
+ * var my_widget = new MyWidget(this);
+ * my_widget.appendTo($(".some-div"));
+ *
+ * With these two lines, the MyWidget instance was inited, rendered, it was inserted into the
+ * DOM inside the ".some-div" div and its events were binded.
+ *
+ * And of course, when you don't need that widget anymore, just do:
+ *
+ * my_widget.destroy();
+ *
+ * That will kill the widget in a clean way and erase its content from the dom.
+ */
+instance.web.Widget = instance.web.Class.extend(instance.web.PropertiesMixin, {
+ // Backbone-ish API
+ tagName: 'div',
+ id: null,
+ className: null,
+ attributes: {},
+ events: {},
/**
- * Proxies a method of the object, in order to keep the right ``this`` on
- * method invocations.
- *
- * This method is similar to ``Function.prototype.bind`` or ``_.bind``, and
- * even more so to ``jQuery.proxy`` with a fundamental difference: its
- * resolution of the method being called is lazy, meaning it will use the
- * method as it is when the proxy is called, not when the proxy is created.
- *
- * Other methods will fix the bound method to what it is when creating the
- * binding/proxy, which is fine in most javascript code but problematic in
- * OpenERP Web where developers may want to replace existing callbacks with
- * theirs.
- *
- * The semantics of this precisely replace closing over the method call.
+ * The name of the QWeb template that will be used for rendering. Must be
+ * redefined in subclasses or the default render() method can not be used.
*
- * @param {String|Function} method function or name of the method to invoke
- * @returns {Function} proxied method
+ * @type string
*/
- proxy: function (method) {
- var self = this;
- return function () {
- var fn = (typeof method === 'string') ? self[method] : method;
- return fn.apply(self, arguments);
- }
- }
-});
-
-instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, {
+ template: null,
/**
* Constructs the widget and sets its parent if a parent is given.
*
* @constructs instance.web.Widget
- * @extends instance.web.CallbackEnabled
*
* @param {instance.web.Widget} parent Binds the current instance to the given Widget instance.
* When that widget is destroyed by calling destroy(), the current instance will be
* destroyed too. Can be null.
*/
init: function(parent) {
- instance.web.CallbackEnabledMixin.init.call(this);
+ instance.web.PropertiesMixin.init.call(this);
this.setParent(parent);
+ // Bind on_/do_* methods to this
+ // We might remove this automatic binding in the future
+ for (var name in this) {
+ if(typeof(this[name]) == "function") {
+ if((/^on_|^do_/).test(name)) {
+ this[name] = this[name].bind(this);
+ }
+ }
+ }
+ // FIXME: this should not be
+ this.setElement(this._make_descriptive());
+ this.session = instance.session;
},
/**
* Destroys the current widget, also destroys all its children before destroying itself.
@@ -574,94 +603,32 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, {
*/
start: function() {
return $.when();
- }
-});
-
-// Classes
-
-instance.web.CallbackEnabled = instance.web.Class.extend(instance.web.CallbackEnabledMixin, {
- init: function() {
- instance.web.CallbackEnabledMixin.init.call(this);
- }
-});
-
-/**
- * Base class for all visual components. Provides a lot of functionalities helpful
- * for the management of a part of the DOM.
- *
- * Widget handles:
- * - Rendering with QWeb.
- * - Life-cycle management and parenting (when a parent is destroyed, all its children are
- * destroyed too).
- * - Insertion in DOM.
- *
- * Guide to create implementations of the Widget class:
- * ==============================================
- *
- * Here is a sample child class:
- *
- * MyWidget = instance.base.Widget.extend({
- * // the name of the QWeb template to use for rendering
- * template: "MyQWebTemplate",
- *
- * init: function(parent) {
- * this._super(parent);
- * // stuff that you want to init before the rendering
- * },
- * start: function() {
- * // stuff you want to make after the rendering, `this.$el` holds a correct value
- * this.$el.find(".my_button").click(/* an example of event binding * /);
- *
- * // if you have some asynchronous operations, it's a good idea to return
- * // a promise in start()
- * var promise = this.rpc(...);
- * return promise;
- * }
- * });
- *
- * Now this class can simply be used with the following syntax:
- *
- * var my_widget = new MyWidget(this);
- * my_widget.appendTo($(".some-div"));
- *
- * With these two lines, the MyWidget instance was inited, rendered, it was inserted into the
- * DOM inside the ".some-div" div and its events were binded.
- *
- * And of course, when you don't need that widget anymore, just do:
- *
- * my_widget.destroy();
- *
- * That will kill the widget in a clean way and erase its content from the dom.
- */
-instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, {
- // Backbone-ish API
- tagName: 'div',
- id: null,
- className: null,
- attributes: {},
- events: {},
+ },
/**
- * The name of the QWeb template that will be used for rendering. Must be
- * redefined in subclasses or the default render() method can not be used.
+ * Proxies a method of the object, in order to keep the right ``this`` on
+ * method invocations.
*
- * @type string
- */
- template: null,
- /**
- * Constructs the widget and sets its parent if a parent is given.
+ * This method is similar to ``Function.prototype.bind`` or ``_.bind``, and
+ * even more so to ``jQuery.proxy`` with a fundamental difference: its
+ * resolution of the method being called is lazy, meaning it will use the
+ * method as it is when the proxy is called, not when the proxy is created.
*
- * @constructs instance.web.Widget
- * @extends instance.web.CallbackEnabled
+ * Other methods will fix the bound method to what it is when creating the
+ * binding/proxy, which is fine in most javascript code but problematic in
+ * OpenERP Web where developers may want to replace existing callbacks with
+ * theirs.
*
- * @param {instance.web.Widget} parent Binds the current instance to the given Widget instance.
- * When that widget is destroyed by calling destroy(), the current instance will be
- * destroyed too. Can be null.
+ * The semantics of this precisely replace closing over the method call.
+ *
+ * @param {String|Function} method function or name of the method to invoke
+ * @returns {Function} proxied method
*/
- init: function(parent) {
- instance.web.WidgetMixin.init.call(this,parent);
- // FIXME: this should not be
- this.setElement(this._make_descriptive());
- this.session = instance.session;
+ proxy: function (method) {
+ var self = this;
+ return function () {
+ var fn = (typeof method === 'string') ? self[method] : method;
+ return fn.apply(self, arguments);
+ }
},
/**
* Renders the element. The default implementation renders the widget using QWeb,
@@ -678,7 +645,6 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, {
}
this.replaceElement($el);
},
-
/**
* Re-sets the widget's root element and replaces the old root element
* (if any) by the new one in the DOM.
@@ -808,10 +774,10 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, {
}
return false;
},
- rpc: function(url, data, success, error) {
- var def = $.Deferred().done(success).fail(error);
+ rpc: function(url, data, options) {
+ var def = $.Deferred();
var self = this;
- instance.session.rpc(url, data).done(function() {
+ instance.session.rpc(url, data, options).done(function() {
if (!self.isDestroyed())
def.resolve.apply(def, arguments);
}).fail(function() {
@@ -941,7 +907,7 @@ instance.web.Registry = instance.web.Class.extend({
}
});
-instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
+instance.web.JsonRPC = instance.web.Class.extend(instance.web.PropertiesMixin, {
triggers: {
'request': 'Request sent',
'response': 'Response received',
@@ -950,13 +916,12 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
},
/**
* @constructs instance.web.JsonRPC
- * @extends instance.web.CallbackEnabled
*
* @param {String} [server] JSON-RPC endpoint hostname
* @param {String} [port] JSON-RPC endpoint port
*/
init: function() {
- this._super();
+ instance.web.PropertiesMixin.init.call(this);
this.server = null;
this.debug = ($.deparam($.param.querystring()).debug != undefined);
},
@@ -1266,12 +1231,14 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
*
* @param {String} url RPC endpoint
* @param {Object} params call parameters
+ * @param {Object} options additional options for rpc call
* @param {Function} success_callback function to execute on RPC call success
* @param {Function} error_callback function to execute on RPC call failure
* @returns {jQuery.Deferred} jquery-provided ajax deferred
*/
- rpc: function(url, params) {
+ rpc: function(url, params, options) {
var self = this;
+ options = options || {};
// url can be an $.ajax option object
if (_.isString(url)) {
url = { url: url };
@@ -1286,10 +1253,12 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
id: _.uniqueId('r')
};
var deferred = $.Deferred();
- this.trigger('request', url, payload);
+ if (! options.shadow)
+ this.trigger('request', url, payload);
var request = this.rpc_function(url, payload).done(
function (response, textStatus, jqXHR) {
- self.trigger('response', response);
+ if (! options.shadow)
+ self.trigger('response', response);
if (!response.error) {
if (url.url === '/web/session/eval_domain_and_context') {
self.test_eval(params, response.result);
@@ -1303,7 +1272,8 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
}
).fail(
function(jqXHR, textStatus, errorThrown) {
- self.trigger('response_failed', jqXHR);
+ if (! options.shadow)
+ self.trigger('response_failed', jqXHR);
var error = {
code: -32098,
message: "XmlHttpRequestError " + errorThrown,
@@ -1311,7 +1281,7 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
};
deferred.reject(error, $.Event());
});
- // Allow deferred user to disable on_rpc_error in fail
+ // Allow deferred user to disable rpc_error call in fail
deferred.fail(function() {
deferred.fail(function(error, event) {
if (!event.isDefaultPrevented()) {
@@ -1344,9 +1314,18 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
// extracted from payload to set on the url
var data = {
session_id: this.session_id,
- id: payload.id
+ id: payload.id,
+ sid: this.httpsessionid,
};
- url.url = this.get_url(url.url);
+
+ var set_sid = function (response, textStatus, jqXHR) {
+ // If response give us the http session id, we store it for next requests...
+ if (response.httpsessionid) {
+ self.httpsessionid = response.httpsessionid;
+ }
+ };
+
+ url.url = this.url(url.url, null);
var ajax = _.extend({
type: "GET",
dataType: 'jsonp',
@@ -1361,7 +1340,7 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
if(payload_url.length < 2000) {
// Direct jsonp request
ajax.data.r = payload_str;
- return $.ajax(ajax);
+ return $.ajax(ajax).done(set_sid);
} else {
// Indirect jsonp request
var ifid = _.uniqueId('oe_rpc_iframe');
@@ -1399,11 +1378,20 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
});
// append the iframe to the DOM (will trigger the first load)
$form.after($iframe);
- return deferred;
+ return deferred.done(set_sid);
}
},
- get_url: function (file) {
- return this.prefix + file;
+
+ url: function(path, params) {
+ var qs = '';
+ if (!_.isNull(params)) {
+ params = _.extend(params || {}, {session_id: this.session_id});
+ if (this.httpsessionid) {
+ params.sid = this.httpsessionid;
+ }
+ qs = '?' + $.param(params);
+ }
+ return this.prefix + path + qs;
},
});
diff --git a/addons/web/static/src/js/coresetup.js b/addons/web/static/src/js/coresetup.js
index b7dac303520..7ddefa301f2 100644
--- a/addons/web/static/src/js/coresetup.js
+++ b/addons/web/static/src/js/coresetup.js
@@ -3,10 +3,13 @@
*--------------------------------------------------------*/
var console;
if (!console) {
- console = {log: function () {}};
-}
-if (!console.debug) {
- console.debug = console.log;
+ // Even IE9 only exposes console object if debug window opened
+ console = {};
+ ('log error debug info warn assert clear dir dirxml trace group'
+ + ' groupCollapsed groupEnd time timeEnd profile profileEnd count'
+ + ' exception').split(/\s+/).forEach(function(property) {
+ console[property] = _.identity;
+ });
}
openerp.web.coresetup = function(instance) {
@@ -19,9 +22,9 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
this.name = instance._session_id;
this.qweb_mutex = new $.Mutex();
},
- rpc: function(url, params) {
+ rpc: function(url, params, options) {
params.session_id = this.session_id;
- return this._super(url, params);
+ return this._super(url, params, options);
},
/**
* Setup a sessionm
@@ -53,7 +56,7 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
this.session_id = this.get_cookie('session_id');
return this.session_reload().then(function(result) {
var modules = instance._modules.join(',');
- var deferred = self.rpc('/web/webclient/qweblist', {mods: modules}).then(self.do_load_qweb);
+ var deferred = self.rpc('/web/webclient/qweblist', {mods: modules}).then(self.load_qweb.bind(self));
if(self.session_is_valid()) {
return deferred.then(function() { return self.load_modules(); });
}
@@ -155,28 +158,26 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
load_modules: function() {
var self = this;
return this.rpc('/web/session/modules', {}).then(function(result) {
- var lang = self.user_context.lang,
- all_modules = _.uniq(self.module_list.concat(result));
- var params = { mods: all_modules, lang: lang};
+ var all_modules = _.uniq(self.module_list.concat(result));
var to_load = _.difference(result, self.module_list).join(',');
self.module_list = all_modules;
- var loaded = self.rpc('/web/webclient/translations', params).done(function(trans) {
- instance.web._t.database.set_bundle(trans);
- });
- var file_list = ["/web/static/lib/datejs/globalization/" + lang.replace("_", "-") + ".js"];
+ var loaded = self.load_translations();
+ var datejs_locale = "/web/static/lib/datejs/globalization/" + self.user_context.lang.replace("_", "-") + ".js";
+
+ var file_list = [ datejs_locale ];
if(to_load.length) {
loaded = $.when(
loaded,
- self.rpc('/web/webclient/csslist', {mods: to_load}).done(self.do_load_css),
- self.rpc('/web/webclient/qweblist', {mods: to_load}).then(self.do_load_qweb),
+ self.rpc('/web/webclient/csslist', {mods: to_load}).done(self.load_css.bind(self)),
+ self.rpc('/web/webclient/qweblist', {mods: to_load}).then(self.load_qweb.bind(self)),
self.rpc('/web/webclient/jslist', {mods: to_load}).done(function(files) {
file_list = file_list.concat(files);
})
);
}
return loaded.then(function () {
- return self.do_load_js(file_list);
+ return self.load_js(file_list);
}).done(function() {
self.on_modules_loaded();
self.trigger('module_loaded');
@@ -190,29 +191,35 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
});
});
},
- do_load_css: function (files) {
+ load_translations: function() {
+ var params = { mods: this.module_list, lang: this.user_context.lang };
+ return this.rpc('/web/webclient/translations', params).done(function(trans) {
+ instance.web._t.database.set_bundle(trans);
+ });
+ },
+ load_css: function (files) {
var self = this;
_.each(files, function (file) {
$('head').append($('<link>', {
- 'href': self.get_url(file),
+ 'href': self.url(file, null),
'rel': 'stylesheet',
'type': 'text/css'
}));
});
},
- do_load_js: function(files) {
+ load_js: function(files) {
var self = this;
var d = $.Deferred();
- if(files.length != 0) {
+ if(files.length !== 0) {
var file = files.shift();
var tag = document.createElement('script');
tag.type = 'text/javascript';
- tag.src = self.get_url(file);
+ tag.src = self.url(file, null);
tag.onload = tag.onreadystatechange = function() {
if ( (tag.readyState && tag.readyState != "loaded" && tag.readyState != "complete") || tag.onload_done )
return;
tag.onload_done = true;
- self.do_load_js(files).done(function () {
+ self.load_js(files).done(function () {
d.resolve();
});
};
@@ -223,7 +230,7 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
}
return d;
},
- do_load_qweb: function(files) {
+ load_qweb: function(files) {
var self = this;
_.each(files, function(file) {
self.qweb_mutex.exec(function() {
@@ -453,6 +460,16 @@ $.fn.getAttributes = function() {
}
return o;
}
+$.fn.openerpClass = function(additionalClass) {
+ // This plugin should be applied on top level elements
+ additionalClass = additionalClass || '';
+ if (!!$.browser.msie) {
+ additionalClass += ' openerp_ie';
+ }
+ return this.each(function() {
+ $(this).addClass('openerp ' + additionalClass);
+ });
+};
/** Jquery extentions */
$.Mutex = (function() {
diff --git a/addons/web/static/src/js/data.js b/addons/web/static/src/js/data.js
index 2f8679794b0..524a88bdc48 100644
--- a/addons/web/static/src/js/data.js
+++ b/addons/web/static/src/js/data.js
@@ -278,9 +278,10 @@ instance.web.Model = instance.web.Class.extend({
* @param {String} method name of the method to call
* @param {Array} [args] positional arguments
* @param {Object} [kwargs] keyword arguments
+ * @param {Object} [options] additional options for the rpc() method
* @returns {jQuery.Deferred<>} call result
*/
- call: function (method, args, kwargs) {
+ call: function (method, args, kwargs, options) {
args = args || [];
kwargs = kwargs || {};
if (!_.isArray(args)) {
@@ -294,7 +295,7 @@ instance.web.Model = instance.web.Class.extend({
method: method,
args: args,
kwargs: kwargs
- });
+ }, options);
},
/**
* Fetches a Query instance bound to this model, for searching
@@ -358,17 +359,16 @@ instance.web.Model = instance.web.Class.extend({
},
});
-instance.web.DataSet = instance.web.CallbackEnabled.extend({
+instance.web.DataSet = instance.web.Class.extend(instance.web.PropertiesMixin, {
/**
* Collection of OpenERP records, used to share records and the current selection between views.
*
* @constructs instance.web.DataSet
- * @extends instance.web.CallbackEnabled
*
* @param {String} model the OpenERP model this dataset will manage
*/
init: function(parent, model, context) {
- this._super(parent);
+ instance.web.PropertiesMixin.init.call(this);
this.model = model;
this.context = context || {};
this.index = null;
@@ -722,10 +722,10 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({
this.last_default_get = {};
},
default_get: function(fields, options) {
- return this._super(fields, options).done(this.on_default_get);
- },
- on_default_get: function(res) {
- this.last_default_get = res;
+ var self = this;
+ return this._super(fields, options).done(function(res) {
+ self.last_default_get = res;
+ });
},
create: function(data) {
var cached = {id:_.uniqueId(this.virtual_id_prefix), values: data,
diff --git a/addons/web/static/src/js/formats.js b/addons/web/static/src/js/formats.js
index 03b2c5b775d..e586b94db6b 100644
--- a/addons/web/static/src/js/formats.js
+++ b/addons/web/static/src/js/formats.js
@@ -83,6 +83,42 @@ instance.web.strip_raw_chars = function (value) {
var normalize_format = function (format) {
return Date.normalizeFormat(instance.web.strip_raw_chars(format));
};
+
+/**
+ * Check with a scary heuristic if the value is a bin_size or not.
+ * If not, compute an approximate size out of the base64 encoded string.
+ *
+ * @param {String} value original format
+ */
+instance.web.binary_to_binsize = function (value) {
+ if (!value) {
+ return instance.web.human_size(0);
+ }
+ if (value.substr(0, 10).indexOf(' ') == -1) {
+ // Computing approximate size out of base64 encoded string
+ // http://en.wikipedia.org/wiki/Base64#MIME
+ return instance.web.human_size(value.length / 1.37);
+ } else {
+ // already bin_size
+ return value;
+ }
+};
+
+/**
+ * Returns a human readable size
+ *
+ * @param {Number} numner of bytes
+ */
+instance.web.human_size = function(size) {
+ var units = _t("Bytes,Kb,Mb,Gb,Tb,Pb,Eb,Zb,Yb").split(',');
+ var i = 0;
+ while (size >= 1024) {
+ size /= 1024;
+ ++i;
+ }
+ return size.toFixed(2) + ' ' + units[i];
+};
+
/**
* Formats a single atomic value based on a field descriptor
*
diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js
index e84ea66ff9a..0f25b2484c3 100644
--- a/addons/web/static/src/js/search.js
+++ b/addons/web/static/src/js/search.js
@@ -349,7 +349,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
$.when(load_view)
.then(function(r) {
self.search_view_loaded(r)
- }).fail(function () {
+ }, function () {
self.ready.reject.apply(null, arguments);
});
}
@@ -459,7 +459,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
complete_global_search: function (req, resp) {
$.when.apply(null, _(this.inputs).chain()
.invoke('complete', req.term)
- .value()).done(function () {
+ .value()).then(function () {
resp(_(_(arguments).compact()).flatten(true));
});
},
@@ -527,7 +527,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
childView.on('blurred', self, self.proxy('childBlurred'));
});
- $.when.apply(null, started).done(function () {
+ $.when.apply(null, started).then(function () {
var input_to_focus;
// options.at: facet inserted at given index, focus next input
// otherwise just focus last input
@@ -608,7 +608,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
// add Filters to this.inputs, need view.controls filled
(new instance.web.search.Filters(this));
// add custom filters to this.inputs
- (new instance.web.search.CustomFilters(this));
+ this.custom_filters = new instance.web.search.CustomFilters(this);
// add Advanced to this.inputs
(new instance.web.search.Advanced(this));
},
@@ -635,16 +635,41 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
// load defaults
var defaults_fetched = $.when.apply(null, _(this.inputs).invoke(
- 'facet_for_defaults', this.defaults)).done(function () {
- self.query.reset(_(arguments).compact(), {preventSearch: true});
- });
-
+ 'facet_for_defaults', this.defaults))
+ .then(this.proxy('setup_default_query'));
+
return $.when(drawer_started, defaults_fetched)
.done(function () {
self.trigger("search_view_loaded", data);
self.ready.resolve();
});
},
+ setup_default_query: function () {
+ // Hacky implementation of CustomFilters#facet_for_defaults ensure
+ // CustomFilters will be ready (and CustomFilters#filters will be
+ // correctly filled) by the time this method executes.
+ var custom_filters = this.custom_filters.filters;
+ if (!_(custom_filters).isEmpty()) {
+ // Check for any is_default custom filter
+ var personal_filter = _(custom_filters).find(function (filter) {
+ return filter.user_id && filter.is_default;
+ });
+ if (personal_filter) {
+ this.custom_filters.enable_filter(personal_filter, true);
+ return;
+ }
+
+ var global_filter = _(custom_filters).find(function (filter) {
+ return !filter.user_id && filter.is_default;
+ });
+ if (global_filter) {
+ this.custom_filters.enable_filter(global_filter, true);
+ return;
+ }
+ }
+ // No custom filter, or no is_default custom filter, apply view defaults
+ this.query.reset(_(arguments).compact(), {preventSearch: true});
+ },
/**
* Extract search data from the view's facets.
*
@@ -1460,10 +1485,15 @@ instance.web.search.ManyToOneField = instance.web.search.CharField.extend({
instance.web.search.CustomFilters = instance.web.search.Input.extend({
template: 'SearchView.CustomFilters',
_in_drawer: true,
+ init: function () {
+ this.is_ready = $.Deferred();
+ this._super.apply(this, arguments);
+ },
start: function () {
var self = this;
this.model = new instance.web.Model('ir.filters');
this.filters = {};
+ this.$filters = {};
this.view.query
.on('remove', function (facet) {
if (!facet.get('is_custom_filter')) {
@@ -1479,24 +1509,78 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({
// FIXME: local eval of domain and context to get rid of special endpoint
return this.rpc('/web/searchview/get_filters', {
model: this.view.model
- }).then(this.proxy('set_filters'));
+ })
+ .then(this.proxy('set_filters'))
+ .then(function () {
+ self.is_ready.resolve(null);
+ }, function () {
+ self.is_ready.reject();
+ });
+ },
+ /**
+ * Special implementation delaying defaults until CustomFilters is loaded
+ */
+ facet_for_defaults: function () {
+ return this.is_ready;
+ },
+ /**
+ * Generates a mapping key (in the filters and $filter mappings) for the
+ * filter descriptor object provided (as returned by ``get_filters``).
+ *
+ * The mapping key is guaranteed to be unique for a given (user_id, name)
+ * pair.
+ *
+ * @param {Object} filter
+ * @param {String} filter.name
+ * @param {Number|Pair<Number, String>} [filter.user_id]
+ * @return {String} mapping key corresponding to the filter
+ */
+ key_for: function (filter) {
+ var user_id = filter.user_id;
+ var uid = (user_id instanceof Array) ? user_id[0] : user_id;
+ return _.str.sprintf('(%s)%s', uid, filter.name);
+ },
+ /**
+ * Generates a :js:class:`~instance.web.search.Facet` descriptor from a
+ * filter descriptor
+ *
+ * @param {Object} filter
+ * @param {String} filter.name
+ * @param {Object} [filter.context]
+ * @param {Array} [filter.domain]
+ * @return {Object}
+ */
+ facet_for: function (filter) {
+ return {
+ category: _t("Custom Filter"),
+ icon: 'M',
+ field: {
+ get_context: function () { return filter.context; },
+ get_groupby: function () { return [filter.context]; },
+ get_domain: function () { return filter.domain; }
+ },
+ is_custom_filter: true,
+ values: [{label: filter.name, value: null}]
+ };
},
clear_selection: function () {
this.$('li.oe_selected').removeClass('oe_selected');
},
append_filter: function (filter) {
var self = this;
- var key = _.str.sprintf('(%s)%s', filter.user_id, filter.name);
+ var key = this.key_for(filter);
var $filter;
- if (key in this.filters) {
- $filter = this.filters[key];
+ if (key in this.$filters) {
+ $filter = this.$filters[key];
} else {
var id = filter.id;
- $filter = this.filters[key] = $('<li></li>')
+ this.filters[key] = filter;
+ $filter = this.$filters[key] = $('<li></li>')
.appendTo(this.$('.oe_searchview_custom_list'))
.addClass(filter.user_id ? 'oe_searchview_custom_private'
: 'oe_searchview_custom_public')
+ .toggleClass('oe_searchview_custom_default', filter.is_default)
.text(filter.name);
$('<a class="oe_searchview_custom_delete">x</a>')
@@ -1504,33 +1588,30 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({
e.stopPropagation();
self.model.call('unlink', [id]).done(function () {
$filter.remove();
+ delete self.$filters[key];
+ delete self.filters[key];
});
})
.appendTo($filter);
}
$filter.unbind('click').click(function () {
- self.view.query.reset([{
- category: _t("Custom Filter"),
- icon: 'M',
- field: {
- get_context: function () { return filter.context; },
- get_groupby: function () { return [filter.context]; },
- get_domain: function () { return filter.domain; }
- },
- is_custom_filter: true,
- values: [{label: filter.name, value: null}]
- }]);
- $filter.addClass('oe_selected');
+ self.enable_filter(filter);
});
},
+ enable_filter: function (filter, preventSearch) {
+ this.view.query.reset([this.facet_for(filter)], {
+ preventSearch: preventSearch || false});
+ this.$filters[this.key_for(filter)].addClass('oe_selected');
+ },
set_filters: function (filters) {
_(filters).map(_.bind(this.append_filter, this));
},
save_current: function () {
var self = this;
var $name = this.$('input:first');
- var private_filter = !this.$('input:last').prop('checked');
+ var private_filter = !this.$('#oe_searchview_custom_public').prop('checked');
+ var set_as_default = this.$('#oe_searchview_custom_default').prop('checked');
var search = this.view.build_search_data();
this.rpc('/web/session/eval_domain_and_context', {
@@ -1546,7 +1627,8 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({
user_id: private_filter ? instance.session.uid : false,
model_id: self.view.model,
context: results.context,
- domain: results.domain
+ domain: results.domain,
+ is_default: set_as_default
};
// FIXME: current context?
return self.model.call('create_or_replace', [filter]).done(function (id) {
diff --git a/addons/web/static/src/js/testing.js b/addons/web/static/src/js/testing.js
new file mode 100644
index 00000000000..bf39f543734
--- /dev/null
+++ b/addons/web/static/src/js/testing.js
@@ -0,0 +1,393 @@
+// Test support structures and methods for OpenERP
+openerp.testing = {};
+(function (testing) {
+ var dependencies = {
+ corelib: [],
+ coresetup: ['corelib'],
+ data: ['corelib', 'coresetup'],
+ dates: [],
+ formats: ['coresetup', 'dates'],
+ chrome: ['corelib', 'coresetup'],
+ views: ['corelib', 'coresetup', 'data', 'chrome'],
+ search: ['data', 'coresetup', 'formats'],
+ list: ['views', 'data'],
+ form: ['data', 'views', 'list', 'formats'],
+ list_editable: ['list', 'form', 'data'],
+ };
+
+ testing.dependencies = window['oe_all_dependencies'] || [];
+ testing.current_module = null;
+ testing.templates = { };
+ testing.add_template = function (name) {
+ var xhr = QWeb2.Engine.prototype.get_xhr();
+ xhr.open('GET', name, false);
+ xhr.send(null);
+ (testing.templates[testing.current_module] =
+ testing.templates[testing.current_module] || [])
+ .push(xhr.responseXML);
+ };
+ /**
+ * Function which does not do anything
+ */
+ testing.noop = function () { };
+ /**
+ * Alter provided instance's ``session`` attribute to make response
+ * mockable:
+ *
+ * * The ``responses`` parameter can be used to provide a map of (RPC)
+ * paths (e.g. ``/web/view/load``) to a function returning a response
+ * to the query.
+ * * ``instance.session`` grows a ``responses`` attribute which is
+ * a map of the same (and is in fact initialized to the ``responses``
+ * parameter if one is provided)
+ *
+ * Note that RPC requests to un-mocked URLs will be rejected with an
+ * error message: only explicitly specified urls will get a response.
+ *
+ * Mocked sessions will *never* perform an actual RPC connection.
+ *
+ * @param instance openerp instance being initialized
+ * @param {Object} [responses]
+ */
+ testing.mockifyRPC = function (instance, responses) {
+ var session = instance.session;
+ session.responses = responses || {};
+ session.rpc_function = function (url, payload) {
+ var fn, params;
+ var needle = payload.params.model + ':' + payload.params.method;
+ if (url.url === '/web/dataset/call_kw'
+ && needle in this.responses) {
+ fn = this.responses[needle];
+ params = [
+ payload.params.args || [],
+ payload.params.kwargs || {}
+ ];
+ } else {
+ fn = this.responses[url.url];
+ params = [payload];
+ }
+
+ if (!fn) {
+ return $.Deferred().reject({}, 'failed',
+ _.str.sprintf("Url %s not found in mock responses, with arguments %s",
+ url.url, JSON.stringify(payload.params))
+ ).promise();
+ }
+ try {
+ return $.when(fn.apply(null, params)).then(function (result) {
+ // Wrap for RPC layer unwrapper thingy
+ return {result: result};
+ });
+ } catch (e) {
+ // not sure why this looks like that
+ return $.Deferred().reject({}, 'failed', String(e));
+ }
+ };
+ };
+
+ var StackProto = {
+ execute: function (fn) {
+ var args = [].slice.call(arguments, 1);
+ // Warning: here be dragons
+ var i = 0, setups = this.setups, teardowns = this.teardowns;
+ var d = $.Deferred();
+
+ var succeeded, failed;
+ var success = function () {
+ succeeded = _.toArray(arguments);
+ return teardown();
+ };
+ var failure = function () {
+ // save first failure
+ if (!failed) {
+ failed = _.toArray(arguments);
+ }
+ // chain onto next teardown
+ return teardown();
+ };
+
+ var setup = function () {
+ // if setup to execute
+ if (i < setups.length) {
+ var f = setups[i] || testing.noop;
+ $.when(f.apply(null, args)).then(function () {
+ ++i;
+ setup();
+ }, failure);
+ } else {
+ $.when(fn.apply(null, args)).then(success, failure);
+ }
+ };
+ var teardown = function () {
+ // if teardown to execute
+ if (i > 0) {
+ var f = teardowns[--i] || testing.noop;
+ $.when(f.apply(null, args)).then(teardown, failure);
+ } else {
+ if (failed) {
+ d.reject.apply(d, failed);
+ } else if (succeeded) {
+ d.resolve.apply(d, succeeded);
+ } else {
+ throw new Error("Didn't succeed or fail?");
+ }
+ }
+ };
+ setup();
+
+ return d;
+ },
+ push: function (setup, teardown) {
+ return _.extend(Object.create(StackProto), {
+ setups: this.setups.concat([setup]),
+ teardowns: this.teardowns.concat([teardown])
+ });
+ },
+ unshift: function (setup, teardown) {
+ return _.extend(Object.create(StackProto), {
+ setups: [setup].concat(this.setups),
+ teardowns: [teardown].concat(this.teardowns)
+ });
+ }
+ };
+ /**
+ *
+ * @param {Function} [setup]
+ * @param {Function} [teardown]
+ * @return {*}
+ */
+ testing.Stack = function (setup, teardown) {
+ return _.extend(Object.create(StackProto), {
+ setups: setup ? [setup] : [],
+ teardowns: teardown ? [teardown] : []
+ });
+ };
+
+ var db = window['oe_db_info'];
+ testing.section = function (name, options, body) {
+ if (_.isFunction(options)) {
+ body = options;
+ options = {};
+ }
+ _.defaults(options, {
+ setup: testing.noop,
+ teardown: testing.noop
+ });
+
+ QUnit.module(testing.current_module + '.' + name, {_oe: options});
+ body(testing.case);
+ };
+ testing.case = function (name, options, callback) {
+ if (_.isFunction(options)) {
+ callback = options;
+ options = {};
+ }
+
+ var module = testing.current_module;
+ var module_index = _.indexOf(testing.dependencies, module);
+ var module_deps = testing.dependencies.slice(
+ // If module not in deps (because only tests, no JS) -> indexOf
+ // returns -1 -> index becomes 0 -> replace with ``undefined`` so
+ // Array#slice returns a full copy
+ 0, module_index + 1 || undefined);
+
+ // Serialize options for this precise test case
+ // WARNING: typo is from jquery, do not fix!
+ var env = QUnit.config.currentModuleTestEnviroment;
+ // section setup
+ // case setup
+ // test
+ // case teardown
+ // section teardown
+ var case_stack = testing.Stack()
+ .push(env._oe.setup, env._oe.teardown)
+ .push(options.setup, options.teardown);
+ var opts = _.defaults({}, options, env._oe);
+ // FIXME: if this test is ignored, will still query
+ if (opts.rpc === 'rpc' && !db) {
+ QUnit.config.autostart = false;
+ db = {
+ source: null,
+ supadmin: null,
+ password: null
+ };
+ var $msg = $('<form style="margin: 0 1em 1em;">')
+ .append('<h3>A test needs to clone a database</h3>')
+ .append('<h4>Please provide the source clone information</h4>')
+ .append(' Source DB: ').append('<input name="source">').append('<br>')
+ .append(' DB Password: ').append('<input name="supadmin">').append('<br>')
+ .append('Admin Password: ').append('<input name="password">').append('<br>')
+ .append('<input type="submit" value="OK"/>')
+ .submit(function (e) {
+ e.preventDefault();
+ e.stopPropagation();
+ db.source = $msg.find('input[name=source]').val();
+ db.supadmin = $msg.find('input[name=supadmin]').val();
+ db.password = $msg.find('input[name=password]').val();
+ QUnit.start();
+ $.unblockUI();
+ });
+ $.blockUI({
+ message: $msg,
+ css: {
+ fontFamily: 'monospace',
+ textAlign: 'left',
+ whiteSpace: 'pre-wrap',
+ cursor: 'default'
+ }
+ });
+ }
+
+ QUnit.test(name, function () {
+ var instance;
+ if (!opts.dependencies) {
+ instance = openerp.init(module_deps);
+ } else {
+ // empty-but-specified dependencies actually allow running
+ // without loading any module into the instance
+
+ // TODO: clean up this mess
+ var d = opts.dependencies.slice();
+ // dependencies list should be in deps order, reverse to make
+ // loading order from last
+ d.reverse();
+ var di = 0;
+ while (di < d.length) {
+ var m = /^web\.(\w+)$/.exec(d[di]);
+ if (m) {
+ d[di] = m[1];
+ }
+ d.splice.apply(d, [di+1, 0].concat(
+ _(dependencies[d[di]]).reverse()));
+ ++di;
+ }
+
+ instance = openerp.init("fuck your shit, don't load anything you cunt");
+ _(d).chain()
+ .reverse()
+ .uniq()
+ .each(function (module) {
+ openerp.web[module](instance);
+ });
+ }
+ if (_.isNumber(opts.asserts)) {
+ expect(opts.asserts);
+ }
+
+ if (opts.templates) {
+ for(var i=0; i<module_deps.length; ++i) {
+ var dep = module_deps[i];
+ var templates = testing.templates[dep];
+ if (_.isEmpty(templates)) { continue; }
+
+ for (var j=0; j < templates.length; ++j) {
+ instance.web.qweb.add_template(templates[j]);
+ }
+ }
+ }
+
+ var $fixture = $('#qunit-fixture');
+
+ var mock, async = false;
+ switch (opts.rpc) {
+ case 'mock':
+ async = true;
+ testing.mockifyRPC(instance);
+ mock = function (spec, handler) {
+ instance.session.responses[spec] = handler;
+ };
+ break;
+ case 'rpc':
+ async = true;
+ (function () {
+ // Bunch of random base36 characters
+ var dbname = 'test_' + Math.random().toString(36).slice(2);
+ // Add db setup/teardown at the start of the stack
+ case_stack = case_stack.unshift(function (instance) {
+ // FIXME hack: don't want the session to go through shitty loading process of everything
+ instance.session.session_init = testing.noop;
+ instance.session.load_modules = testing.noop;
+ instance.session.session_bind();
+ return instance.session.rpc('/web/database/duplicate', {
+ fields: [
+ {name: 'super_admin_pwd', value: db.supadmin},
+ {name: 'db_original_name', value: db.source},
+ {name: 'db_name', value: dbname}
+ ]
+ }).then(function (result) {
+ if (result.error) {
+ return $.Deferred().reject(result.error).promise();
+ }
+ return instance.session.session_authenticate(
+ dbname, 'admin', db.password, true);
+ });
+ }, function (instance) {
+ return instance.session.rpc('/web/database/drop', {
+ fields: [
+ {name: 'drop_pwd', value: db.supadmin},
+ {name: 'drop_db', value: dbname}
+ ]
+ }).then(function (result) {
+ if (result.error) {
+ return $.Deferred().reject(result.error).promise();
+ }
+ return result;
+ });
+ });
+ })();
+ }
+
+ // Always execute tests asynchronously
+ stop();
+ var timeout;
+ case_stack.execute(function () {
+ var result = callback.apply(null, arguments);
+ if (!(result && _.isFunction(result.then))) {
+ if (async) {
+ ok(false, "asynchronous test cases must return a promise");
+ }
+ } else {
+ if (!_.isNumber(opts.asserts)) {
+ ok(false, "asynchronous test cases must specify the "
+ + "number of assertions they expect");
+ }
+ }
+
+ return $.Deferred(function (d) {
+ $.when(result).then(function () {
+ d.resolve.apply(d, arguments)
+ }, function () {
+ d.reject.apply(d, arguments);
+ });
+ if (async || (result && result.then)) {
+ // async test can be either implicit async (rpc) or
+ // promise-returning
+ timeout = setTimeout(function () {
+ QUnit.config.semaphore = 1;
+ d.reject({message: "Test timed out"});
+ }, 2000);
+ }
+ });
+ }, instance, $fixture, mock).always(function () {
+ if (timeout) { clearTimeout(timeout); }
+ start();
+ }).fail(function (error) {
+ if (options.fail_on_rejection === false) {
+ return;
+ }
+ var message;
+ if (typeof error !== 'object'
+ || typeof error.message !== 'string') {
+ message = JSON.stringify([].slice.apply(arguments));
+ } else {
+ message = error.message;
+ if (error.data && error.data.debug) {
+ message += '\n\n' + error.data.debug;
+ }
+ }
+
+ ok(false, message);
+ });
+ });
+ };
+})(openerp.testing);
diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js
index 906f1d8fe36..dad622fb7f9 100644
--- a/addons/web/static/src/js/view_form.js
+++ b/addons/web/static/src/js/view_form.js
@@ -173,10 +173,14 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
} else {
this.$el.find('.oe_form_buttons').replaceWith(this.$buttons);
}
- this.$buttons.on('click', '.oe_form_button_create', this.on_button_create);
- this.$buttons.on('click', '.oe_form_button_edit', this.on_button_edit);
- this.$buttons.on('click', '.oe_form_button_save', this.on_button_save);
- this.$buttons.on('click', '.oe_form_button_cancel', this.on_button_cancel);
+ this.$buttons.on('click', '.oe_form_button_create',
+ this.guard_active(this.on_button_create));
+ this.$buttons.on('click', '.oe_form_button_edit',
+ this.guard_active(this.on_button_edit));
+ this.$buttons.on('click', '.oe_form_button_save',
+ this.guard_active(this.on_button_save));
+ this.$buttons.on('click', '.oe_form_button_cancel',
+ this.guard_active(this.on_button_cancel));
if (this.options.footer_to_buttons) {
this.$el.find('footer').appendTo(this.$buttons);
}
@@ -618,6 +622,8 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
return self._process_save(save_obj).then(function() {
save_obj.ret = _.toArray(arguments);
return iterate();
+ }, function() {
+ save_obj.error = true;
});
}
return $.when();
@@ -801,6 +807,8 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
var save_obj = {prepend_on_create: prepend_on_create, ret: null};
this.save_list.push(save_obj);
return this._process_operations().then(function() {
+ if (save_obj.error)
+ return $.Deferred().reject();
return $.when.apply($, save_obj.ret);
});
},
@@ -1187,14 +1195,36 @@ instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInt
});
}
},
+ view_arch_to_dom_node: function(arch) {
+ // Historic mess for views arch
+ //
+ // server:
+ // -> got xml as string
+ // -> parse to xml and manipulate domains and contexts
+ // -> convert to json
+ // client:
+ // -> got view as json
+ // -> convert back to xml as string
+ // -> parse it as xml doc (manipulate button@type for IE)
+ // -> convert back to string
+ // -> parse it as dom element with jquery
+ // -> for each widget, convert node to json
+ //
+ // Wow !!!
+ var xml = instance.web.json_node_to_xml(arch);
+
+ var doc = $.parseXML('<div class="oe_form">' + xml + '</div>');
+ $('button', doc).each(function() {
+ $(this).attr('data-button-type', $(this).attr('type'));
+ });
+ xml = instance.web.xml_to_str(doc);
+ return $(xml);
+ },
render_to: function($target) {
var self = this;
this.$target = $target;
- // TODO: I know this will save the world and all the kitten for a moment,
- // but one day, we will have to get rid of xml2json
- var xml = instance.web.json_node_to_xml(this.fvg.arch);
- this.$form = $('<div class="oe_form">' + xml + '</div>');
+ this.$form = this.view_arch_to_dom_node(this.fvg.arch);
this.process_version();
@@ -1863,6 +1893,7 @@ instance.web.form.FormWidget = instance.web.Widget.extend(instance.web.form.Invi
instance.web.form.WidgetButton = instance.web.form.FormWidget.extend({
template: 'WidgetButton',
init: function(field_manager, node) {
+ node.attrs.type = node.attrs['data-button-type'];
this._super(field_manager, node);
this.force_disabled = false;
this.string = (this.node.attrs.string || '').replace(/_/g, '');
@@ -2152,17 +2183,10 @@ instance.web.form.AbstractField = instance.web.form.FormWidget.extend(instance.w
},
set_dimensions: function (height, width) {
- // remove width css property
- this.$el.css('width', '');
- // extract style (without width)
- var old_style = this.$el.attr('style');
- // jQuery doesn't understand/use !important
- var style = 'width:' + width + 'px !important;';
- if (old_style) {
- style += old_style
- }
- this.$el.attr('style', style);
- this.$el.css('minHeight', height);
+ this.$el.css({
+ width: width,
+ minHeight: height
+ });
},
commit_value: function() {
return $.when();
@@ -2386,7 +2410,7 @@ instance.web.DateTimeWidget = instance.web.Widget.extend({
self.$input.focus();
return;
}
- self.picker('setDate', self.value ? instance.web.auto_str_to_date(self.value) : new Date());
+ self.picker('setDate', self.get('value') ? instance.web.auto_str_to_date(self.get('value')) : new Date());
self.$input_picker.show();
self.picker('show');
self.$input_picker.hide();
@@ -2639,22 +2663,18 @@ instance.web.form.FieldBoolean = instance.web.form.AbstractField.extend({
}
});
+/**
+ The progressbar field expect a float from 0 to 100.
+*/
instance.web.form.FieldProgressBar = instance.web.form.AbstractField.extend({
template: 'FieldProgressBar',
- start: function() {
- this._super.apply(this, arguments);
+ render_value: function() {
this.$el.progressbar({
- value: this.get('value'),
+ value: this.get('value') || 0,
disabled: this.get("effective_readonly")
});
- },
- render_value: function() {
- var show_value = Number(this.get('value'));
- if (isNaN(show_value)) {
- show_value = 0;
- }
- var formatted_value = instance.web.format_value(show_value, { type : 'float' }, '0');
- this.$el.progressbar('option', 'value', show_value).find('span').html(formatted_value + '%');
+ var formatted_value = instance.web.format_value(this.get('value') || 0, { type : 'float' });
+ this.$('span').html(formatted_value + '%');
}
});
@@ -3106,7 +3126,7 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
minLength: 0,
delay: 0
});
- this.$input.autocomplete("widget").addClass("openerp");
+ this.$input.autocomplete("widget").openerpClass();
// used to correct a bug when selecting an element by pushing 'enter' in an editable list
this.$input.keyup(function(e) {
if (e.which === 13) { // ENTER
@@ -3817,26 +3837,13 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({
this._super.apply(this, arguments);
},
do_delete: function (ids) {
- var self = this;
- var next = $.when();
- var _super = this._super;
- // handle deletion of an item which does not exist
- // TODO: better handle that in the editable list?
- var false_id_index = _(ids).indexOf(false);
- if (false_id_index !== -1) {
- ids.splice(false_id_index, 1);
- next = this.cancel_edition(true);
- }
- return next.then(function () {
- // wheeee
- var confirm = window.confirm;
- window.confirm = function () { return true; };
- try {
- return _super.call(self, ids);
- } finally {
- window.confirm = confirm;
- }
- });
+ var confirm = window.confirm;
+ window.confirm = function () { return true; };
+ try {
+ return this._super(ids);
+ } finally {
+ window.confirm = confirm;
+ }
}
});
instance.web.form.One2ManyGroups = instance.web.ListView.Groups.extend({
@@ -3999,13 +4006,18 @@ instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(in
self._drop_shown = true;
});
self.tags = self.$text.textext()[0].tags();
- self.$text.focusout(function() {
- self.$text.trigger("setInputData", "");
- }).keydown(function(e) {
- if (e.which === $.ui.keyCode.TAB && self._drop_shown) {
- self.$text.textext()[0].autocomplete().selectFromDropdown();
- }
- });
+ self.$text
+ .focusin(function () {
+ self.trigger('focused');
+ })
+ .focusout(function() {
+ self.$text.trigger("setInputData", "");
+ self.trigger('blurred');
+ }).keydown(function(e) {
+ if (e.which === $.ui.keyCode.TAB && self._drop_shown) {
+ self.$text.textext()[0].autocomplete().selectFromDropdown();
+ }
+ });
},
set_value: function(value_) {
value_ = value_ || [];
@@ -4794,15 +4806,6 @@ instance.web.form.FieldBinary = instance.web.form.AbstractField.extend(instance.
this.$el.find('button.oe_form_binary_file_save').click(this.on_save_as);
this.$el.find('.oe_form_binary_file_clear').click(this.on_clear);
},
- human_filesize : function(size) {
- var units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
- var i = 0;
- while (size >= 1024) {
- size /= 1024;
- ++i;
- }
- return size.toFixed(2) + ' ' + units[i];
- },
on_file_change: function(e) {
var self = this;
var file_node = e.target;
@@ -4850,6 +4853,7 @@ instance.web.form.FieldBinary = instance.web.form.AbstractField.extend(instance.
link.href = "data:application/octet-stream;base64," + value;
} else {
instance.web.blockUI();
+ var c = instance.webclient.crashmanager;
this.session.get_file({
url: '/web/binary/saveas_ajax',
data: {data: JSON.stringify({
@@ -4860,7 +4864,7 @@ instance.web.form.FieldBinary = instance.web.form.AbstractField.extend(instance.
context: this.view.dataset.get_context()
})},
complete: instance.web.unblockUI,
- error: instance.webclient.crashmanager.on_rpc_error
+ error: c.rpc_error.bind(c)
});
ev.stopPropagation();
return false;
@@ -4919,7 +4923,7 @@ instance.web.form.FieldBinaryFile = instance.web.form.FieldBinary.extend({
on_file_uploaded_and_valid: function(size, name, content_type, file_base64) {
this.binary_value = true;
this.internal_set_value(file_base64);
- var show_value = name + " (" + this.human_filesize(size) + ")";
+ var show_value = name + " (" + instance.web.human_size(size) + ")";
this.$el.find('input').eq(0).val(show_value);
this.set_filename(name);
},
@@ -4939,12 +4943,16 @@ instance.web.form.FieldBinaryImage = instance.web.form.FieldBinary.extend({
if (this.get('value') && ! /^\d+(\.\d*)? \w+$/.test(this.get('value'))) {
url = 'data:image/png;base64,' + this.get('value');
} else if (this.get('value')) {
- var id = escape(JSON.stringify(this.view.datarecord.id || null));
+ var id = JSON.stringify(this.view.datarecord.id || null);
var field = this.name;
if (this.options.preview_image)
field = this.options.preview_image;
- url = '/web/binary/image?session_id=' + this.session.session_id + '&model=' +
- this.view.dataset.model +'&id=' + id + '&field=' + field + '&t=' + (new Date().getTime());
+ url = this.session.url('/web/binary/image', {
+ model: this.view.dataset.model,
+ id: id,
+ field: field,
+ t: (new Date().getTime()),
+ });
} else {
url = this.placeholder;
}
@@ -4983,14 +4991,14 @@ instance.web.form.FieldBinaryImage = instance.web.form.FieldBinary.extend({
* Options on attribute ; "blockui" {Boolean} block the UI or not
* during the file is uploading
*/
-instance.web.form.FieldOne2ManyBinaryMultiFiles = instance.web.form.AbstractField.extend({
+instance.web.form.FieldMany2ManyBinaryMultiFiles = instance.web.form.AbstractField.extend({
template: "FieldBinaryFileUploader",
init: function(field_manager, node) {
this._super(field_manager, node);
this.field_manager = field_manager;
this.node = node;
- if(this.field.type != "one2many" || this.field.relation != 'ir.attachment') {
- throw "The type of the field '"+this.field.string+"' must be a one2many field with a relation to 'ir.attachment' model.";
+ if(this.field.type != "many2many" || this.field.relation != 'ir.attachment') {
+ throw "The type of the field '"+this.field.string+"' must be a many2many field with a relation to 'ir.attachment' model.";
}
this.ds_file = new instance.web.DataSetSearch(this, 'ir.attachment');
this.fileupload_id = _.uniqueId('oe_fileupload_temp');
@@ -5000,21 +5008,85 @@ instance.web.form.FieldOne2ManyBinaryMultiFiles = instance.web.form.AbstractFiel
this._super(this);
this.$el.on('change', 'input.oe_form_binary_file', this.on_file_change );
},
+ set_value: function(value_) {
+ var value_ = value_ || [];
+ var self = this;
+ var ids = [];
+ _.each(value_, function(command) {
+ if (isNaN(command) && command.id == undefined) {
+ switch (command[0]) {
+ case commands.CREATE:
+ ids = ids.concat(command[2]);
+ return;
+ case commands.REPLACE_WITH:
+ ids = ids.concat(command[2]);
+ return;
+ case commands.UPDATE:
+ ids = ids.concat(command[2]);
+ return;
+ case commands.LINK_TO:
+ ids = ids.concat(command[1]);
+ return;
+ case commands.DELETE:
+ ids = _.filter(ids, function (id) { return id != command[1];});
+ return;
+ case commands.DELETE_ALL:
+ ids = [];
+ return;
+ }
+ } else {
+ ids.push(command);
+ }
+ });
+ this._super( ids );
+ },
get_value: function() {
return _.map(this.get('value'), function (value) { return commands.link_to( value.id ); });
},
get_file_url: function (attachment) {
- return instance.origin + '/web/binary/saveas?session_id=' + this.session.session_id + '&model=ir.attachment&field=datas&filename_field=datas_fname&id=' + attachment['id'];
+ return this.session.url('/web/binary/saveas', {model: 'ir.attachment', field: 'datas', filename_field: 'datas_fname', id: attachment['id']});
+ },
+ read_name_values : function () {
+ var self = this;
+ // select the list of id for a get_name
+ var values = [];
+ _.each(this.get('value'), function (val) {
+ if (typeof val != 'object') {
+ values.push(val);
+ }
+ });
+ // send request for get_name
+ if (values.length) {
+ return this.ds_file.call('read', [values, ['id', 'name', 'datas_fname']]).done(function (datas) {
+ _.each(datas, function (data) {
+ data.no_unlink = true;
+ data.url = self.session.url('/web/binary/saveas', {model: 'ir.attachment', field: 'datas', filename_field: 'datas_fname', id: data.id});
+
+ _.each(self.get('value'), function (val, key) {
+ if(val == data.id) {
+ self.get('value')[key] = data;
+ }
+ });
+ });
+ });
+ } else {
+ return $.when(this.get('value'));
+ }
},
render_value: function () {
- var render = $(instance.web.qweb.render('FieldBinaryFileUploader.files', {'widget': this}));
- render.on('click', '.oe_delete', _.bind(this.on_file_delete, this));
- this.$('.oe_placeholder_files, .oe_attachments').replaceWith( render );
+ var self = this;
+ this.read_name_values().then(function (datas) {
- // reinit input type file
- var $input = this.$('input.oe_form_binary_file');
- $input.after($input.clone(true)).remove();
- this.$(".oe_fileupload").show();
+ var render = $(instance.web.qweb.render('FieldBinaryFileUploader.files', {'widget': self}));
+ render.on('click', '.oe_delete', _.bind(self.on_file_delete, self));
+ self.$('.oe_placeholder_files, .oe_attachments').replaceWith( render );
+
+ // reinit input type file
+ var $input = self.$('input.oe_form_binary_file');
+ $input.after($input.clone(true)).remove();
+ self.$(".oe_fileupload").show();
+
+ });
},
on_file_change: function (event) {
event.stopPropagation();
@@ -5030,7 +5102,7 @@ instance.web.form.FieldOne2ManyBinaryMultiFiles = instance.web.form.AbstractFiel
}
// block UI or not
- if(this.node.attrs.blockui) {
+ if(this.node.attrs.blockui>0) {
instance.web.blockUI();
}
@@ -5064,7 +5136,7 @@ instance.web.form.FieldOne2ManyBinaryMultiFiles = instance.web.form.AbstractFiel
},
on_file_loaded: function (event, result) {
// unblock UI
- if(this.node.attrs.blockui) {
+ if(this.node.attrs.blockui>0) {
instance.web.unblockUI();
}
@@ -5094,7 +5166,7 @@ instance.web.form.FieldOne2ManyBinaryMultiFiles = instance.web.form.AbstractFiel
if(file_id != this.get('value')[i].id){
files.push(this.get('value')[i]);
}
- else {
+ else if(!this.get('value')[i].no_unlink) {
this.ds_file.unlink([file_id]);
}
}
@@ -5256,7 +5328,7 @@ instance.web.form.widgets = new instance.web.Registry({
'progressbar': 'instance.web.form.FieldProgressBar',
'image': 'instance.web.form.FieldBinaryImage',
'binary': 'instance.web.form.FieldBinaryFile',
- 'one2many_binary': 'instance.web.form.FieldOne2ManyBinaryMultiFiles',
+ 'many2many_binary': 'instance.web.form.FieldMany2ManyBinaryMultiFiles',
'statusbar': 'instance.web.form.FieldStatus',
'monetary': 'instance.web.form.FieldMonetary',
});
diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js
index 03a48297eca..df74674a343 100644
--- a/addons/web/static/src/js/view_list.js
+++ b/addons/web/static/src/js/view_list.js
@@ -1,6 +1,6 @@
openerp.web.list = function (instance) {
var _t = instance.web._t,
- _lt = instance.web._lt;
+ _lt = instance.web._lt;
var QWeb = instance.web.qweb;
instance.web.views.add('list', 'instance.web.ListView');
instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListView# */ {
@@ -503,12 +503,17 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
return this.reload_content();
},
reload_record: function (record) {
+ var self = this;
return this.dataset.read_ids(
[record.get('id')],
_.pluck(_(this.columns).filter(function (r) {
return r.tag === 'field';
}), 'name')
).done(function (records) {
+ if (!records[0]) {
+ self.records.remove(record);
+ return;
+ }
_(records[0]).each(function (value, key) {
record.set(key, value, {silent: true});
});
@@ -1557,9 +1562,8 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we
}
});
-var DataGroup = instance.web.CallbackEnabled.extend({
+var DataGroup = instance.web.Class.extend({
init: function(parent, model, domain, context, group_by, level) {
- this._super(parent, null);
this.model = new instance.web.Model(model, context, domain);
this.group_by = group_by;
this.context = context;
@@ -2152,7 +2156,7 @@ instance.web.list.Boolean = instance.web.list.Column.extend({
* @private
*/
_format: function (row_data, options) {
- return _.str.sprintf('<input type="checkbox" %s disabled="disabled"/>',
+ return _.str.sprintf('<input type="checkbox" %s readonly="readonly"/>',
row_data[this.id].value ? 'checked="checked"' : '');
}
});
@@ -2164,20 +2168,24 @@ instance.web.list.Binary = instance.web.list.Column.extend({
*/
_format: function (row_data, options) {
var text = _t("Download");
- var download_url = _.str.sprintf(
- '/web/binary/saveas?session_id=%s&model=%s&field=%s&id=%d',
- instance.session.session_id, options.model, this.id, options.id);
- if (this.filename) {
- download_url += '&filename_field=' + this.filename;
- if (row_data[this.filename]) {
- text = _.str.sprintf(_t("Download \"%s\""), instance.web.format_value(
- row_data[this.filename].value, {type: 'char'}));
+ var value = row_data[this.id].value;
+ var download_url;
+ if (value && value.substr(0, 10).indexOf(' ') == -1) {
+ download_url = "data:application/octet-stream;base64," + value;
+ } else {
+ download_url = this.session.url('/web/binary/saveas', {model: options.model, field: this.id, id: options.id});
+ if (this.filename) {
+ download_url += '&filename_field=' + this.filename;
}
}
- return _.template('<a href="<%-href%>"><%-text%></a> (%<-size%>)', {
+ if (this.filename && row_data[this.filename]) {
+ text = _.str.sprintf(_t("Download \"%s\""), instance.web.format_value(
+ row_data[this.filename].value, {type: 'char'}));
+ }
+ return _.template('<a href="<%-href%>"><%-text%></a> (<%-size%>)', {
text: text,
href: download_url,
- size: row_data[this.id].value
+ size: instance.web.binary_to_binsize(value),
});
}
});
@@ -2217,7 +2225,7 @@ instance.web.list.Many2OneButton = instance.web.list.Column.extend({
});
instance.web.list.Many2Many = instance.web.list.Column.extend({
_format: function (row_data, options) {
- if (row_data[this.id].value) {
+ if (!_.isEmpty(row_data[this.id].value)) {
// If value, use __display version for printing
row_data[this.id] = row_data[this.id + '__display'];
}
diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js
index 37ce8c4f8b4..1abe3caab39 100644
--- a/addons/web/static/src/js/view_list_editable.js
+++ b/addons/web/static/src/js/view_list_editable.js
@@ -77,6 +77,15 @@ openerp.web.list_editable = function (instance) {
do_edit: function (index, id, dataset) {
_.extend(this.dataset, dataset);
},
+ do_delete: function (ids) {
+ var _super = this._super.bind(this);
+ var next = this.editor.is_editing()
+ ? this.cancel_edition(true)
+ : $.when();
+ return next.then(function () {
+ return _super(ids);
+ });
+ },
editable: function () {
return this.fields_view.arch.attrs.editable
|| this._context_editable
@@ -117,13 +126,6 @@ openerp.web.list_editable = function (instance) {
e.preventDefault();
self.cancel_edition();
});
- this.$el
- .off('click', 'tbody td:not(.oe_list_field_cell)')
- .on('click', 'tbody td:not(.oe_list_field_cell)', function () {
- if (!self.editor.is_editing()) {
- self.start_edition();
- }
- });
this.editor.destroy();
// Editor is not restartable due to formview not being
// restartable
@@ -164,13 +166,23 @@ openerp.web.list_editable = function (instance) {
* @returns {$.Deferred}
*/
ensure_saved: function () {
- var self = this;
- return this.saving_mutex.exec(function() {
- if (!self.editor.is_editing()) {
- return $.when();
- }
- return self.save_edition();
- });
+ return this.save_edition();
+ },
+ /**
+ * Builds a record with the provided id (``false`` for a creation),
+ * setting all columns with ``false`` value so code which relies on
+ * having an actual value behaves correctly
+ *
+ * @param {*} id
+ * @return {instance.web.list.Record}
+ */
+ make_empty_record: function (id) {
+ var attrs = {id: id};
+ _(this.columns).chain()
+ .filter(function (x) { return x.tag === 'field'})
+ .pluck('name')
+ .each(function (field) { attrs[field] = false; });
+ return new instance.web.list.Record(attrs);
},
/**
* Set up the edition of a record of the list view "inline"
@@ -186,12 +198,7 @@ openerp.web.list_editable = function (instance) {
if (record) {
item = record.attributes;
} else {
- var attrs = {id: false};
- _(this.columns).chain()
- .filter(function (x) { return x.tag === 'field'})
- .pluck('name')
- .each(function (field) { attrs[field] = false; });
- record = new instance.web.list.Record(attrs);
+ record = this.make_empty_record(false);
this.records.add(record, {
at: this.prepends_on_create() ? 0 : null});
}
@@ -269,29 +276,34 @@ openerp.web.list_editable = function (instance) {
*/
save_edition: function () {
var self = this;
- return this.with_event('save', {
- editor: this.editor,
- form: this.editor.form,
- cancel: false
- }, function () {
- return this.editor.save().then(function (attrs) {
- var created = false;
- var record = self.records.get(attrs.id);
- if (!record) {
- // new record
- created = true;
- record = self.records.find(function (r) {
- return !r.get('id');
- }).set('id', attrs.id);
- }
- // onwrite callback could be altering & reloading the
- // record which has *just* been saved, so first perform all
- // onwrites then do a final reload of the record
- return self.handle_onwrite(record)
- .then(function () {
- return self.reload_record(record); })
- .then(function () {
- return { created: created, record: record }; });
+ return self.saving_mutex.exec(function() {
+ if (!self.editor.is_editing()) {
+ return $.when();
+ }
+ return self.with_event('save', {
+ editor: self.editor,
+ form: self.editor.form,
+ cancel: false
+ }, function () {
+ return self.editor.save().then(function (attrs) {
+ var created = false;
+ var record = self.records.get(attrs.id);
+ if (!record) {
+ // new record
+ created = true;
+ record = self.records.find(function (r) {
+ return !r.get('id');
+ }).set('id', attrs.id);
+ }
+ // onwrite callback could be altering & reloading the
+ // record which has *just* been saved, so first perform all
+ // onwrites then do a final reload of the record
+ return self.handle_onwrite(record)
+ .then(function () {
+ return self.reload_record(record); })
+ .then(function () {
+ return { created: created, record: record }; });
+ });
});
});
},
@@ -391,7 +403,7 @@ openerp.web.list_editable = function (instance) {
if (!record) {
// insert after the source record
var index = this.records.indexOf(source_record) + 1;
- record = new instance.web.list.Record({id: id});
+ record = this.make_empty_record(id);
this.records.add(record, {at: index});
this.dataset.ids.splice(index, 0, id);
}
diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js
index 53132e454d1..641821152b2 100644
--- a/addons/web/static/src/js/views.js
+++ b/addons/web/static/src/js/views.js
@@ -213,8 +213,12 @@ instance.web.ActionManager = instance.web.Widget.extend({
if (run_action) {
this.null_action();
action_loaded = this.do_action(state.action);
- instance.webclient.menu.has_been_loaded.done(function() {
- instance.webclient.menu.open_action(state.action);
+ $.when(action_loaded || null).done(function() {
+ instance.webclient.menu.has_been_loaded.done(function() {
+ if (self.inner_action && self.inner_action.id) {
+ instance.webclient.menu.open_action(self.inner_action.id);
+ }
+ });
});
}
}
@@ -306,15 +310,15 @@ instance.web.ActionManager = instance.web.Widget.extend({
}
var widget = executor.widget();
if (executor.action.target === 'new') {
- if (this.dialog_widget && ! this.dialog_widget.isDestroyed())
+ if (this.dialog_widget && !this.dialog_widget.isDestroyed()) {
this.dialog_widget.destroy();
- if (this.dialog === null || this.dialog.isDestroyed()) {
- this.dialog = new instance.web.Dialog(this, {
- dialogClass: executor.klass,
- });
- this.dialog.on("closing", null, options.on_close);
- this.dialog.init_dialog();
}
+ this.dialog_stop();
+ this.dialog = new instance.web.Dialog(this, {
+ dialogClass: executor.klass,
+ });
+ this.dialog.on("closing", null, options.on_close);
+ this.dialog.init_dialog();
this.dialog.dialog_title = executor.action.name;
if (widget instanceof instance.web.ViewManager) {
_.extend(widget.flags, {
@@ -397,6 +401,7 @@ instance.web.ActionManager = instance.web.Widget.extend({
}).done(function(res) {
action = _.clone(action);
action.context = res.context;
+ var c = instance.webclient.crashmanager;
self.session.get_file({
url: '/web/report',
data: {action: JSON.stringify(action)},
@@ -407,7 +412,7 @@ instance.web.ActionManager = instance.web.Widget.extend({
}
self.dialog_stop();
},
- error: instance.webclient.crashmanager.on_rpc_error
+ error: c.rpc_error.bind(c)
})
});
},
@@ -504,7 +509,7 @@ instance.web.ViewManager = instance.web.Widget.extend({
.find('.oe_view_manager_switch a').filter('[data-view-type="' + view_type + '"]')
.parent().addClass('active');
- r = $.when(view_promise).done(function () {
+ return $.when(view_promise).done(function () {
_.each(_.keys(self.views), function(view_name) {
var controller = self.views[view_name].controller;
if (controller) {
@@ -520,7 +525,6 @@ instance.web.ViewManager = instance.web.Widget.extend({
});
self.trigger('switch_mode', view_type, no_store, view_options);
});
- return r;
},
do_create_view: function(view_type) {
// Lazy loading of views
@@ -785,8 +789,8 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({
name: "JS Tests",
target: 'new',
type : 'ir.actions.act_url',
- url: '/web/static/test/test.html'
- })
+ url: '/web/tests?mod=*'
+ });
break;
case 'perm_read':
var ids = current_view.get_selected_ids();
@@ -1098,11 +1102,11 @@ instance.web.Sidebar = instance.web.Widget.extend({
on_attachments_loaded: function(attachments) {
var self = this;
var items = [];
- var prefix = this.session.origin + '/web/binary/saveas?session_id=' + self.session.session_id + '&model=ir.attachment&field=datas&filename_field=name&id=';
+ var prefix = this.session.url('/web/binary/saveas', {model: 'ir.attachment', field: 'datas', filename_field: 'name'});
_.each(attachments,function(a) {
a.label = a.name;
if(a.type === "binary") {
- a.url = prefix + a.id + '&t=' + (new Date().getTime());
+ a.url = prefix + '&id=' + a.id + '&t=' + (new Date().getTime());
}
});
self.items['files'] = attachments;
@@ -1235,11 +1239,12 @@ instance.web.View = instance.web.Widget.extend({
});
}, null);
} else {
+ self.do_action({"type":"ir.actions.act_window_close"});
return result_handler();
}
};
- if (action_data.special) {
+ if (action_data.special === 'cancel') {
return handler({"type":"ir.actions.act_window_close"});
} else if (action_data.type=="object") {
var args = [[record_id]], additional_args = [];
@@ -1277,6 +1282,27 @@ instance.web.View = instance.web.Widget.extend({
do_hide: function () {
this.$el.hide();
},
+ is_active: function () {
+ var manager = this.getParent();
+ return !manager || !manager.active_view
+ || manager.views[manager.active_view].controller === this;
+ }, /**
+ * Wraps fn to only call it if the current view is the active one. If the
+ * current view is not active, doesn't call fn.
+ *
+ * fn can not return anything, as a non-call to fn can't return anything
+ * either
+ *
+ * @param {Function} fn function to wrap in the active guard
+ */
+ guard_active: function (fn) {
+ var self = this;
+ return function () {
+ if (self.is_active()) {
+ fn.apply(self, arguments);
+ }
+ }
+ },
do_push_state: function(state) {
if (this.getParent() && this.getParent().do_push_state) {
this.getParent().do_push_state(state);
@@ -1384,14 +1410,16 @@ instance.web.json_node_to_xml = function(node, human_readable, indent) {
} else {
return r + '/>';
}
-}
+};
instance.web.xml_to_str = function(node) {
- if (window.ActiveXObject) {
+ if (window.XMLSerializer) {
+ return (new XMLSerializer()).serializeToString(node);
+ } else if (window.ActiveXObject) {
return node.xml;
} else {
- return (new XMLSerializer()).serializeToString(node);
+ throw new Error("Could not serialize XML");
}
-}
+};
instance.web.str_to_xml = function(s) {
if (window.DOMParser) {
var dp = new DOMParser();
diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml
index 215374cb6fb..1b7a5cea53a 100644
--- a/addons/web/static/src/xml/base.xml
+++ b/addons/web/static/src/xml/base.xml
@@ -152,6 +152,32 @@
</tr>
</table>
</form>
+ <form id="db_duplicate" name="duplicate_db_form" style="display: none;">
+ <div class="oe_view_manager oe_view_manager_current">
+ <div class="oe_view_manager_header" style="padding: 8px;">
+ <div class="oe_header_row">
+ <h2 class="oe_view_title">
+ <span class="oe_view_title_text oe_breadcrumb_title">Duplicate Database</span>
+ </h2>
+ <button type="submit" class="oe_button oe_highlight db_duplicate">Duplicate</button>
+ </div>
+ </div>
+ </div>
+ <table align="center" class="db_option_table">
+ <tr>
+ <td><label for="super_admin_pwd">Master password:</label></td>
+ <td><input type="password" name="super_admin_pwd" class="required" value="admin" /></td>
+ </tr>
+ <tr>
+ <td><label for="db_original_name">Original database name:</label></td>
+ <td><input type="text" name="db_original_name" class="required" matches="^[a-zA-Z][a-zA-Z0-9_-]+$" autofocus="true"/></td>
+ </tr>
+ <tr>
+ <td><label for="db_name">New database name:</label></td>
+ <td><input type="text" name="db_name" class="required" matches="^[a-zA-Z][a-zA-Z0-9_-]+$" /></td>
+ </tr>
+ </table>
+ </form>
<form id="db_drop" name="drop_db_form" style="display: none; ">
<div class="oe_view_manager oe_view_manager_current">
<div class="oe_view_manager_header" style="padding: 8px;">
@@ -279,6 +305,7 @@
<div class="oe_secondary_menu_section">Database Management</div>
<ul class="oe_secondary_submenu">
<li><a href="#db_create">Create</a></li>
+ <li><a href="#db_duplicate">Duplicate</a></li>
<li><a href="#db_drop">Drop</a></li>
<li><a href="#db_backup">Backup</a></li>
<li><a href="#db_restore">Restore</a></li>
@@ -1182,26 +1209,28 @@
</t>
<t t-name="FieldBinaryFileUploader.files">
<div class="oe_attachments">
- <t t-if="!widget.get('effective_readonly')" t-foreach="widget.get('value')" t-as="file">
- <div class="oe_attachment">
- <span t-if="(file.upload or file.percent_loaded&lt;100)" t-attf-title="{(file.name || file.filename) + (file.date?' \n('+file.date+')':'' )}" t-attf-name="{file.name || file.filename}">
- <span class="oe_fileuploader_in_process">...Upload in progress...</span>
- <t t-raw="file.name || file.filename"/>
- </span>
- <a t-if="(!file.upload or file.percent_loaded&gt;=100)" t-att-href="file.url" t-attf-title="{(file.name || file.filename) + (file.date?' \n('+file.date+')':'' )}">
- <t t-raw="file.name || file.filename"/>
- </a>
- <t t-if="(!file.upload or file.percent_loaded&gt;=100)">
- <a class="oe_right oe_delete oe_e" title="Delete this file" t-attf-data-id="{file.id}">[</a>
- </t>
- </div>
- </t>
- <t t-if="widget.get('effective_readonly')" t-foreach="widget.get('value')" t-as="file">
- <div>
- <a t-att-href="file.url" t-attf-title="{(file.name || file.filename) + (file.date?' \n('+file.date+')':'' )}">
- <t t-raw="file.name || file.filename"/>
- </a>
- </div>
+ <t t-if="widget.get('value')">
+ <t t-if="!widget.get('effective_readonly')" t-foreach="widget.get('value')" t-as="file">
+ <div class="oe_attachment">
+ <span t-if="(file.upload or file.percent_loaded&lt;100)" t-attf-title="{(file.name || file.filename) + (file.date?' \n('+file.date+')':'' )}" t-attf-name="{file.name || file.filename}">
+ <span class="oe_fileuploader_in_process">...Upload in progress...</span>
+ <t t-raw="file.name || file.filename"/>
+ </span>
+ <a t-if="(!file.upload or file.percent_loaded&gt;=100)" t-att-href="file.url" t-attf-title="{(file.name || file.filename) + (file.date?' \n('+file.date+')':'' )}">
+ <t t-raw="file.name || file.filename"/>
+ </a>
+ <t t-if="(!file.upload or file.percent_loaded&gt;=100)">
+ <a class="oe_right oe_delete oe_e" title="Delete this file" t-attf-data-id="{file.id}">[</a>
+ </t>
+ </div>
+ </t>
+ <t t-if="widget.get('effective_readonly')" t-foreach="widget.get('value')" t-as="file">
+ <div>
+ <a t-att-href="file.url" t-attf-title="{(file.name || file.filename) + (file.date?' \n('+file.date+')':'' )}">
+ <t t-raw="file.name || file.filename"/>
+ </a>
+ </div>
+ </t>
</t>
</div>
</t>
@@ -1512,17 +1541,19 @@
<h3><span class="oe_i">M</span> Custom Filters</h3>
<ul class="oe_searchview_custom_list"/>
<div class="oe_searchview_custom">
- <h4>Save current filter</h4>
- <form>
- <p><input id="oe_searchview_custom_input" placeholder="Filter name"/></p>
- <p><input id="oe_searchview_custom_public" type="checkbox"/>
- <label for="oe_searchview_custom_public">Share with all users</label></p>
- <button>Save</button>
- </form>
+ <h4>Save current filter</h4>
+ <form>
+ <p><input id="oe_searchview_custom_input" placeholder="Filter name"/></p>
+ <p>
+ <input id="oe_searchview_custom_public" type="checkbox"/>
+ <label for="oe_searchview_custom_public">Share with all users</label>
+ <input id="oe_searchview_custom_default" type="checkbox"/>
+ <label for="oe_searchview_custom_default">Use by default</label>
+ </p>
+ <button>Save</button>
+ </form>
</div>
</div>
- <div>
- </div>
</div>
<div t-name="SearchView.advanced" class="oe_searchview_advanced">
diff --git a/addons/web/static/test/Widget.js b/addons/web/static/test/Widget.js
index 3e6cc262565..44b7cfe0dfa 100644
--- a/addons/web/static/test/Widget.js
+++ b/addons/web/static/test/Widget.js
@@ -1,32 +1,7 @@
-$(document).ready(function () {
- var $fix = $('#qunit-fixture');
- var mod = {
- setup: function () {
- instance = window.openerp.init([]);
- window.openerp.web.corelib(instance);
-
- instance.web.qweb = new QWeb2.Engine();
- instance.web.qweb.add_template(
- '<no>' +
- '<t t-name="test.widget.template">' +
- '<ol>' +
- '<li t-foreach="5" t-as="counter" ' +
- 't-attf-class="class-#{counter}">' +
- '<input/>' +
- '<t t-esc="counter"/>' +
- '</li>' +
- '</ol>' +
- '</t>' +
- '<t t-name="test.widget.template-value">' +
- '<p><t t-esc="widget.value"/></p>' +
- '</t>' +
- '</no>');
- }
- };
- var instance;
-
- module('Widget.proxy', mod);
- test('(String)', function () {
+openerp.testing.section('Widget.proxy', {
+ dependencies: ['web.corelib']
+}, function (test) {
+ test('(String)', function (instance) {
var W = instance.web.Widget.extend({
exec: function () {
this.executed = true;
@@ -37,7 +12,7 @@ $(document).ready(function () {
fn();
ok(w.executed, 'should execute the named method in the right context');
});
- test('(String)(*args)', function () {
+ test('(String)(*args)', function (instance) {
var W = instance.web.Widget.extend({
exec: function (arg) {
this.executed = arg;
@@ -49,7 +24,7 @@ $(document).ready(function () {
ok(w.executed, "should execute the named method in the right context");
equal(w.executed, 42, "should be passed the proxy's arguments");
});
- test('(String), include', function () {
+ test('(String), include', function (instance) {
// the proxy function should handle methods being changed on the class
// and should always proxy "by name", to the most recent one
var W = instance.web.Widget.extend({
@@ -67,23 +42,43 @@ $(document).ready(function () {
equal(w.executed, 2, "should be lazily resolved");
});
- test('(Function)', function () {
+ test('(Function)', function (instance) {
var w = new (instance.web.Widget.extend({ }));
var fn = w.proxy(function () { this.executed = true; });
fn();
ok(w.executed, "should set the function's context (like Function#bind)");
});
- test('(Function)(*args)', function () {
+ test('(Function)(*args)', function (instance) {
var w = new (instance.web.Widget.extend({ }));
var fn = w.proxy(function (arg) { this.executed = arg; });
fn(42);
equal(w.executed, 42, "should be passed the proxy's arguments");
});
-
- module('Widget.renderElement', mod);
- test('no template, default', function () {
+});
+openerp.testing.section('Widget.renderElement', {
+ dependencies: ['web.corelib'],
+ setup: function (instance) {
+ instance.web.qweb = new QWeb2.Engine();
+ instance.web.qweb.add_template(
+ '<no>' +
+ '<t t-name="test.widget.template">' +
+ '<ol>' +
+ '<li t-foreach="5" t-as="counter" ' +
+ 't-attf-class="class-#{counter}">' +
+ '<input/>' +
+ '<t t-esc="counter"/>' +
+ '</li>' +
+ '</ol>' +
+ '</t>' +
+ '<t t-name="test.widget.template-value">' +
+ '<p><t t-esc="widget.value"/></p>' +
+ '</t>' +
+ '</no>');
+ }
+}, function (test) {
+ test('no template, default', function (instance) {
var w = new (instance.web.Widget.extend({ }));
var $original = w.$el;
@@ -98,7 +93,7 @@ $(document).ready(function () {
equal(w.el.attributes.length, 0, "should not have generated any attribute");
ok(_.isEmpty(w.$el.html(), "should not have generated any content"));
});
- test('no template, custom tag', function () {
+ test('no template, custom tag', function (instance) {
var w = new (instance.web.Widget.extend({
tagName: 'ul'
}));
@@ -106,7 +101,7 @@ $(document).ready(function () {
equal(w.el.nodeName, 'UL', "should have generated the custom element tag");
});
- test('no template, @id', function () {
+ test('no template, @id', function (instance) {
var w = new (instance.web.Widget.extend({
id: 'foo'
}));
@@ -116,7 +111,7 @@ $(document).ready(function () {
equal(w.$el.attr('id'), 'foo', "should have generated the id attribute");
equal(w.el.id, 'foo', "should also be available via property");
});
- test('no template, @className', function () {
+ test('no template, @className', function (instance) {
var w = new (instance.web.Widget.extend({
className: 'oe_some_class'
}));
@@ -125,7 +120,7 @@ $(document).ready(function () {
equal(w.el.className, 'oe_some_class', "should have the right property");
equal(w.$el.attr('class'), 'oe_some_class', "should have the right attribute");
});
- test('no template, bunch of attributes', function () {
+ test('no template, bunch of attributes', function (instance) {
var w = new (instance.web.Widget.extend({
attributes: {
'id': 'some_id',
@@ -152,7 +147,7 @@ $(document).ready(function () {
equal(w.$el.attr('spoiler'), 'snape kills dumbledore');
});
- test('template', function () {
+ test('template', function (instance) {
var w = new (instance.web.Widget.extend({
template: 'test.widget.template'
}));
@@ -162,9 +157,41 @@ $(document).ready(function () {
equal(w.$el.children().length, 5);
equal(w.el.textContent, '01234');
});
-
- module('Widget.$', mod);
- test('basic-alias', function () {
+ test('repeated', { asserts: 4 }, function (instance, $fix) {
+ var w = new (instance.web.Widget.extend({
+ template: 'test.widget.template-value'
+ }));
+ w.value = 42;
+ return w.appendTo($fix)
+ .done(function () {
+ equal($fix.find('p').text(), '42', "DOM fixture should contain initial value");
+ equal(w.$el.text(), '42', "should set initial value");
+ w.value = 36;
+ w.renderElement();
+ equal($fix.find('p').text(), '36', "DOM fixture should use new value");
+ equal(w.$el.text(), '36', "should set new value");
+ });
+ });
+});
+openerp.testing.section('Widget.$', {
+ dependencies: ['web.corelib'],
+ setup: function (instance) {
+ instance.web.qweb = new QWeb2.Engine();
+ instance.web.qweb.add_template(
+ '<no>' +
+ '<t t-name="test.widget.template">' +
+ '<ol>' +
+ '<li t-foreach="5" t-as="counter" ' +
+ 't-attf-class="class-#{counter}">' +
+ '<input/>' +
+ '<t t-esc="counter"/>' +
+ '</li>' +
+ '</ol>' +
+ '</t>' +
+ '</no>');
+ }
+}, function (test) {
+ test('basic-alias', function (instance) {
var w = new (instance.web.Widget.extend({
template: 'test.widget.template'
}));
@@ -173,9 +200,26 @@ $(document).ready(function () {
ok(w.$('li:eq(3)').is(w.$el.find('li:eq(3)')),
"should do the same thing as calling find on the widget root");
});
-
- module('Widget.events', mod);
- test('delegate', function () {
+});
+openerp.testing.section('Widget.events', {
+ dependencies: ['web.corelib'],
+ setup: function (instance) {
+ instance.web.qweb = new QWeb2.Engine();
+ instance.web.qweb.add_template(
+ '<no>' +
+ '<t t-name="test.widget.template">' +
+ '<ol>' +
+ '<li t-foreach="5" t-as="counter" ' +
+ 't-attf-class="class-#{counter}">' +
+ '<input/>' +
+ '<t t-esc="counter"/>' +
+ '</li>' +
+ '</ol>' +
+ '</t>' +
+ '</no>');
+ }
+}, function (test) {
+ test('delegate', function (instance) {
var a = [];
var w = new (instance.web.Widget.extend({
template: 'test.widget.template',
@@ -199,7 +243,7 @@ $(document).ready(function () {
ok(a[i], "should pass test " + i);
}
});
- test('undelegate', function () {
+ test('undelegate', function (instance) {
var clicked = false, newclicked = false;
var w = new (instance.web.Widget.extend({
template: 'test.widget.template',
@@ -218,22 +262,4 @@ $(document).ready(function () {
ok(!clicked, "undelegate should unbind events delegated");
ok(newclicked, "undelegate should only unbind events it created");
});
-
- module('Widget.renderElement', mod);
- asyncTest('repeated', 4, function () {
- var w = new (instance.web.Widget.extend({
- template: 'test.widget.template-value'
- }));
- w.value = 42;
- w.appendTo($fix)
- .always(start)
- .done(function () {
- equal($fix.find('p').text(), '42', "DOM fixture should contain initial value");
- equal(w.$el.text(), '42', "should set initial value");
- w.value = 36;
- w.renderElement();
- equal($fix.find('p').text(), '36', "DOM fixture should use new value");
- equal(w.$el.text(), '36', "should set new value");
- });
- });
});
diff --git a/addons/web/static/test/class.js b/addons/web/static/test/class.js
index faad4421e31..bb3fb7e12b9 100644
--- a/addons/web/static/test/class.js
+++ b/addons/web/static/test/class.js
@@ -1,30 +1,25 @@
-$(document).ready(function () {
- var openerp;
- module('web-class', {
- setup: function () {
- openerp = window.openerp.init([]);
- window.openerp.web.corelib(openerp);
- }
- });
- test('Basic class creation', function () {
- var C = openerp.web.Class.extend({
+openerp.testing.section('class', {
+ dependencies: ['web.corelib']
+}, function (test) {
+ test('Basic class creation', function (instance) {
+ var C = instance.web.Class.extend({
foo: function () {
return this.somevar;
}
});
- var instance = new C();
- instance.somevar = 3;
+ var i = new C();
+ i.somevar = 3;
- ok(instance instanceof C);
- strictEqual(instance.foo(), 3);
+ ok(i instanceof C);
+ strictEqual(i.foo(), 3);
});
- test('Class initialization', function () {
- var C1 = openerp.web.Class.extend({
+ test('Class initialization', function (instance) {
+ var C1 = instance.web.Class.extend({
init: function () {
this.foo = 3;
}
});
- var C2 = openerp.web.Class.extend({
+ var C2 = instance.web.Class.extend({
init: function (arg) {
this.foo = arg;
}
@@ -36,8 +31,8 @@ $(document).ready(function () {
strictEqual(i1.foo, 3);
strictEqual(i2.foo, 42);
});
- test('Inheritance', function () {
- var C0 = openerp.web.Class.extend({
+ test('Inheritance', function (instance) {
+ var C0 = instance.web.Class.extend({
foo: function () {
return 1;
}
@@ -57,8 +52,8 @@ $(document).ready(function () {
strictEqual(new C1().foo(), 2);
strictEqual(new C2().foo(), 3);
});
- test('In-place extension', function () {
- var C0 = openerp.web.Class.extend({
+ test('In-place extension', function (instance) {
+ var C0 = instance.web.Class.extend({
foo: function () {
return 3;
},
@@ -83,8 +78,8 @@ $(document).ready(function () {
strictEqual(new C0().foo(), 5);
strictEqual(new C0().qux(), 5);
});
- test('In-place extension and inheritance', function () {
- var C0 = openerp.web.Class.extend({
+ test('In-place extension and inheritance', function (instance) {
+ var C0 = instance.web.Class.extend({
foo: function () { return 1; },
bar: function () { return 1; }
});
@@ -101,24 +96,24 @@ $(document).ready(function () {
strictEqual(new C1().foo(), 4);
strictEqual(new C1().bar(), 2);
});
- test('In-place extensions alter existing instances', function () {
- var C0 = openerp.web.Class.extend({
+ test('In-place extensions alter existing instances', function (instance) {
+ var C0 = instance.web.Class.extend({
foo: function () { return 1; },
bar: function () { return 1; }
});
- var instance = new C0();
- strictEqual(instance.foo(), 1);
- strictEqual(instance.bar(), 1);
+ var i = new C0();
+ strictEqual(i.foo(), 1);
+ strictEqual(i.bar(), 1);
C0.include({
foo: function () { return 2; },
bar: function () { return 2 + this._super(); }
});
- strictEqual(instance.foo(), 2);
- strictEqual(instance.bar(), 3);
+ strictEqual(i.foo(), 2);
+ strictEqual(i.bar(), 3);
});
- test('In-place extension of subclassed types', function () {
- var C0 = openerp.web.Class.extend({
+ test('In-place extension of subclassed types', function (instance) {
+ var C0 = instance.web.Class.extend({
foo: function () { return 1; },
bar: function () { return 1; }
});
@@ -126,13 +121,13 @@ $(document).ready(function () {
foo: function () { return 1 + this._super(); },
bar: function () { return 1 + this._super(); }
});
- var instance = new C1();
- strictEqual(instance.foo(), 2);
+ var i = new C1();
+ strictEqual(i.foo(), 2);
C0.include({
foo: function () { return 2; },
bar: function () { return 2 + this._super(); }
});
- strictEqual(instance.foo(), 3);
- strictEqual(instance.bar(), 4);
+ strictEqual(i.foo(), 3);
+ strictEqual(i.bar(), 4);
});
});
diff --git a/addons/web/static/test/evals.js b/addons/web/static/test/evals.js
index 757d98c5bcc..6eec615b199 100644
--- a/addons/web/static/test/evals.js
+++ b/addons/web/static/test/evals.js
@@ -1,18 +1,11 @@
-$(document).ready(function () {
- var openerp;
-
- module("eval.contexts", {
- setup: function () {
- openerp = window.openerp.init([]);
- window.openerp.web.corelib(openerp);
- window.openerp.web.coresetup(openerp);
- }
- });
- test('context_sequences', function () {
+openerp.testing.section('eval.contexts', {
+ dependencies: ['web.coresetup']
+}, function (test) {
+ test('context_sequences', function (instance) {
// Context n should have base evaluation context + all of contexts
// 0..n-1 in its own evaluation context
var active_id = 4;
- var result = openerp.session.test_eval_contexts([
+ var result = instance.session.test_eval_contexts([
{
"__contexts": [
{
@@ -55,8 +48,8 @@ $(document).ready(function () {
record_id: active_id
});
});
- test('non-literal_eval_contexts', function () {
- var result = openerp.session.test_eval_contexts([{
+ test('non-literal_eval_contexts', function (instance) {
+ var result = instance.session.test_eval_contexts([{
"__ref": "compound_context",
"__contexts": [
{"__ref": "context", "__debug": "{'type':parent.type}",
@@ -133,17 +126,15 @@ $(document).ready(function () {
}]);
deepEqual(result, {type: 'out_invoice'});
});
- module('eval.domains', {
- setup: function () {
- openerp = window.openerp.testing.instanceFor('coresetup');
- window.openerp.web.dates(openerp);
- }
- });
- test('current_date', function () {
- var current_date = openerp.web.date_to_str(new Date());
- var result = openerp.session.test_eval_domains(
+});
+openerp.testing.section('eval.contexts', {
+ dependencies: ['web.coresetup', 'web.dates']
+}, function (test) {
+ test('current_date', function (instance) {
+ var current_date = instance.web.date_to_str(new Date());
+ var result = instance.session.test_eval_domains(
[[],{"__ref":"domain","__debug":"[('name','>=',current_date),('name','<=',current_date)]","__id":"5dedcfc96648"}],
- openerp.session.test_eval_get_context());
+ instance.session.test_eval_get_context());
deepEqual(result, [
['name', '>=', current_date],
['name', '<=', current_date]
diff --git a/addons/web/static/test/form.js b/addons/web/static/test/form.js
index aace926a96a..0c8107ce9bc 100644
--- a/addons/web/static/test/form.js
+++ b/addons/web/static/test/form.js
@@ -1,33 +1,21 @@
-$(document).ready(function () {
- var openerp;
- module("form.widget", {
- setup: function () {
- openerp = window.openerp.init([]);
- window.openerp.web.corelib(openerp);
- window.openerp.web.coresetup(openerp);
- window.openerp.web.chrome(openerp);
- // views loader stuff
- window.openerp.web.data(openerp);
- window.openerp.web.views(openerp);
- window.openerp.web.list(openerp);
- window.openerp.web.form(openerp);
- }
- });
- test("compute_domain", function () {
+openerp.testing.section('compute_domain', {
+ dependencies: ['web.form']
+}, function (test) {
+ test("basic", function (instance) {
var fields = {
'a': {value: 3},
'group_method': {value: 'line'},
'select1': {value: 'day'},
'rrule_type': {value: 'monthly'}
};
- ok(openerp.web.form.compute_domain(
+ ok(instance.web.form.compute_domain(
[['a', '=', 3]], fields));
- ok(openerp.web.form.compute_domain(
+ ok(instance.web.form.compute_domain(
[['group_method','!=','count']], fields));
- ok(openerp.web.form.compute_domain(
+ ok(instance.web.form.compute_domain(
[['select1','=','day'], ['rrule_type','=','monthly']], fields));
});
- test("compute_domain or", function () {
+ test("or", function (instance) {
var web = {
'section_id': {value: null},
'user_id': {value: null},
@@ -38,22 +26,22 @@ $(document).ready(function () {
'|', ['user_id','=',3],
['member_ids', 'in', [3]]];
- ok(openerp.web.form.compute_domain(domain, _.extend(
+ ok(instance.web.form.compute_domain(domain, _.extend(
{}, web, {'section_id': {value: 42}})));
- ok(openerp.web.form.compute_domain(domain, _.extend(
+ ok(instance.web.form.compute_domain(domain, _.extend(
{}, web, {'user_id': {value: 3}})));
- ok(openerp.web.form.compute_domain(domain, _.extend(
+ ok(instance.web.form.compute_domain(domain, _.extend(
{}, web, {'member_ids': {value: 3}})));
});
- test("compute_domain not", function () {
+ test("not", function (instance) {
var fields = {
'a': {value: 5},
'group_method': {value: 'line'}
};
- ok(openerp.web.form.compute_domain(
+ ok(instance.web.form.compute_domain(
['!', ['a', '=', 3]], fields));
- ok(openerp.web.form.compute_domain(
+ ok(instance.web.form.compute_domain(
['!', ['group_method','=','count']], fields));
});
});
diff --git a/addons/web/static/test/formats.js b/addons/web/static/test/formats.js
index 4fe07ff0a9c..4536501d71d 100644
--- a/addons/web/static/test/formats.js
+++ b/addons/web/static/test/formats.js
@@ -1,16 +1,8 @@
-$(document).ready(function () {
- var openerp;
-
- module('server-formats', {
- setup: function () {
- openerp = window.openerp.init([]);
- window.openerp.web.corelib(openerp);
- window.openerp.web.coresetup(openerp);
- window.openerp.web.dates(openerp);
- }
- });
- test('Parse server datetime', function () {
- var date = openerp.web.str_to_datetime("2009-05-04 12:34:23");
+openerp.testing.section('server-formats', {
+ dependencies: ['web.coresetup', 'web.dates']
+}, function (test) {
+ test('Parse server datetime', function (instance) {
+ var date = instance.web.str_to_datetime("2009-05-04 12:34:23");
deepEqual(
[date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(),
date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds()],
@@ -20,92 +12,86 @@ $(document).ready(function () {
date.getHours(), date.getMinutes(), date.getSeconds()],
[2009, 5 - 1, 4, 12 - (date.getTimezoneOffset() / 60), 34, 23]);
- var date2 = openerp.web.str_to_datetime('2011-12-10 00:00:00');
+ var date2 = instance.web.str_to_datetime('2011-12-10 00:00:00');
deepEqual(
[date2.getUTCFullYear(), date2.getUTCMonth(), date2.getUTCDate(),
date2.getUTCHours(), date2.getUTCMinutes(), date2.getUTCSeconds()],
[2011, 12 - 1, 10, 0, 0, 0]);
});
- test('Parse server date', function () {
- var date = openerp.web.str_to_date("2009-05-04");
+ test('Parse server date', function (instance) {
+ var date = instance.web.str_to_date("2009-05-04");
deepEqual(
[date.getFullYear(), date.getMonth(), date.getDate()],
[2009, 5 - 1, 4]);
});
- test('Parse server time', function () {
- var date = openerp.web.str_to_time("12:34:23");
+ test('Parse server time', function (instance) {
+ var date = instance.web.str_to_time("12:34:23");
deepEqual(
[date.getHours(), date.getMinutes(), date.getSeconds()],
[12, 34, 23]);
});
-
- module('web-formats', {
- setup: function () {
- openerp = window.openerp.init([]);
- window.openerp.web.corelib(openerp);
- window.openerp.web.coresetup(openerp);
- window.openerp.web.dates(openerp);
- window.openerp.web.formats(openerp);
- }
- });
- test("format_datetime", function () {
- var date = openerp.web.str_to_datetime("2009-05-04 12:34:23");
- var str = openerp.web.format_value(date, {type:"datetime"});
+});
+openerp.testing.section('web-formats', {
+ dependencies: ['web.formats']
+}, function (test) {
+ test("format_datetime", function (instance) {
+ var date = instance.web.str_to_datetime("2009-05-04 12:34:23");
+ var str = instance.web.format_value(date, {type:"datetime"});
equal(str, date.toString("MM/dd/yyyy HH:mm:ss"));
});
- test("format_date", function () {
- var date = openerp.web.str_to_datetime("2009-05-04 12:34:23");
- var str = openerp.web.format_value(date, {type:"date"});
+ test("format_date", function (instance) {
+ var date = instance.web.str_to_datetime("2009-05-04 12:34:23");
+ var str = instance.web.format_value(date, {type:"date"});
equal(str, date.toString("MM/dd/yyyy"));
});
- test("format_time", function () {
- var date = openerp.web.str_to_datetime("2009-05-04 12:34:23");
- var str = openerp.web.format_value(date, {type:"time"});
+ test("format_time", function (instance) {
+ var date = instance.web.str_to_datetime("2009-05-04 12:34:23");
+ var str = instance.web.format_value(date, {type:"time"});
equal(str, date.toString("HH:mm:ss"));
});
- test("format_float_time", function () {
+ test("format_float_time", function (instance) {
strictEqual(
- openerp.web.format_value(1.0, {type:'float', widget:'float_time'}),
+ instance.web.format_value(1.0, {type:'float', widget:'float_time'}),
'01:00');
strictEqual(
- openerp.web.format_value(0.9853, {type:'float', widget:'float_time'}),
+ instance.web.format_value(0.9853, {type:'float', widget:'float_time'}),
'00:59');
strictEqual(
- openerp.web.format_value(0.0085, {type:'float', widget:'float_time'}),
+ instance.web.format_value(0.0085, {type:'float', widget:'float_time'}),
'00:01');
strictEqual(
- openerp.web.format_value(-1.0, {type:'float', widget:'float_time'}),
+ instance.web.format_value(-1.0, {type:'float', widget:'float_time'}),
'-01:00');
strictEqual(
- openerp.web.format_value(-0.9853, {type:'float', widget:'float_time'}),
+ instance.web.format_value(-0.9853, {type:'float', widget:'float_time'}),
'-00:59');
strictEqual(
- openerp.web.format_value(-0.0085, {type:'float', widget:'float_time'}),
+ instance.web.format_value(-0.0085, {type:'float', widget:'float_time'}),
'-00:01');
});
- test("format_float", function () {
+ test("format_float", function (instance) {
var fl = 12.1234;
- var str = openerp.web.format_value(fl, {type:"float"});
+ var str = instance.web.format_value(fl, {type:"float"});
equal(str, "12.12");
- equal(openerp.web.format_value(12.02, {type: 'float'}),
+ equal(instance.web.format_value(12.02, {type: 'float'}),
'12.02');
- equal(openerp.web.format_value(0.0002, {type: 'float', digits: [1, 3]}),
+ equal(instance.web.format_value(0.0002, {type: 'float', digits: [1, 3]}),
'0.000');
- equal(openerp.web.format_value(0.0002, {type: 'float', digits: [1, 4]}),
+ equal(instance.web.format_value(0.0002, {type: 'float', digits: [1, 4]}),
'0.0002');
- equal(openerp.web.format_value(0.0002, {type: 'float', digits: [1, 6]}),
+ equal(instance.web.format_value(0.0002, {type: 'float', digits: [1, 6]}),
'0.000200');
- equal(openerp.web.format_value(1, {type: 'float', digits: [1, 6]}),
+ equal(instance.web.format_value(1, {type: 'float', digits: [1, 6]}),
'1.000000');
- equal(openerp.web.format_value(1, {type: 'float'}),
+ equal(instance.web.format_value(1, {type: 'float'}),
'1.00');
- equal(openerp.web.format_value(-11.25, {type: 'float'}),
+ equal(instance.web.format_value(-11.25, {type: 'float'}),
"-11.25");
- openerp.web._t.database.parameters.grouping = [1, 2, -1];
- equal(openerp.web.format_value(1111111.25, {type: 'float'}),
+ instance.web._t.database.parameters.grouping = [1, 2, -1];
+ equal(instance.web.format_value(1111111.25, {type: 'float'}),
"1111,11,1.25");
- openerp.web._t.database.parameters.grouping = [1, 0];
- equal(openerp.web.format_value(-11.25, {type: 'float'}),
+ instance.web._t.database.parameters.grouping = [1, 0];
+ equal(instance.web.format_value(-11.25, {type: 'float'}),
"-1,1.25");
});
// test("parse_datetime", function () {
@@ -123,29 +109,29 @@ $(document).ready(function () {
// var res = openerp.web.parse_value(val.toString("HH:mm:ss"), {type:"time"});
// equal(val.toString("HH:mm:ss"), res.toString("HH:mm:ss"));
// });
- test('parse_integer', function () {
- var val = openerp.web.parse_value('123,456', {type: 'integer'});
+ test('parse_integer', function (instance) {
+ var val = instance.web.parse_value('123,456', {type: 'integer'});
equal(val, 123456);
- openerp.web._t.database.parameters.thousands_sep = '|';
- var val2 = openerp.web.parse_value('123|456', {type: 'integer'});
+ instance.web._t.database.parameters.thousands_sep = '|';
+ var val2 = instance.web.parse_value('123|456', {type: 'integer'});
equal(val2, 123456);
});
- test("parse_float", function () {
+ test("parse_float", function (instance) {
var str = "134,112.1234";
- var val = openerp.web.parse_value(str, {type:"float"});
+ var val = instance.web.parse_value(str, {type:"float"});
equal(val, 134112.1234);
var str = "-134,112.1234";
- var val = openerp.web.parse_value(str, {type:"float"});
+ var val = instance.web.parse_value(str, {type:"float"});
equal(val, -134112.1234);
- _.extend(openerp.web._t.database.parameters, {
+ _.extend(instance.web._t.database.parameters, {
decimal_point: ',',
thousands_sep: '.'
});
- var val3 = openerp.web.parse_value('123.456,789', {type: 'float'});
+ var val3 = instance.web.parse_value('123.456,789', {type: 'float'});
equal(val3, 123456.789);
});
- test('intersperse', function () {
- var g = openerp.web.intersperse;
+ test('intersperse', function (instance) {
+ var g = instance.web.intersperse;
equal(g("", []), "");
equal(g("0", []), "0");
equal(g("012", []), "012");
@@ -176,60 +162,61 @@ $(document).ready(function () {
equal(g("12345678", [3,3,3,3], '.'), '12.345.678');
equal(g("12345678", [3,0], '.'), '12.345.678');
});
- test('format_integer', function () {
- openerp.web._t.database.parameters.grouping = [3, 3, 3, 3];
- equal(openerp.web.format_value(1000000, {type: 'integer'}),
+ test('format_integer', function (instance) {
+ instance.web._t.database.parameters.grouping = [3, 3, 3, 3];
+ equal(instance.web.format_value(1000000, {type: 'integer'}),
'1,000,000');
- openerp.web._t.database.parameters.grouping = [3, 2, -1];
- equal(openerp.web.format_value(106500, {type: 'integer'}),
+ instance.web._t.database.parameters.grouping = [3, 2, -1];
+ equal(instance.web.format_value(106500, {type: 'integer'}),
'1,06,500');
- openerp.web._t.database.parameters.grouping = [1, 2, -1];
- equal(openerp.web.format_value(106500, {type: 'integer'}),
+ instance.web._t.database.parameters.grouping = [1, 2, -1];
+ equal(instance.web.format_value(106500, {type: 'integer'}),
'106,50,0');
});
- test('format_float', function () {
- openerp.web._t.database.parameters.grouping = [3, 3, 3, 3];
- equal(openerp.web.format_value(1000000, {type: 'float'}),
+ test('format_float', function (instance) {
+ instance.web._t.database.parameters.grouping = [3, 3, 3, 3];
+ equal(instance.web.format_value(1000000, {type: 'float'}),
'1,000,000.00');
- openerp.web._t.database.parameters.grouping = [3, 2, -1];
- equal(openerp.web.format_value(106500, {type: 'float'}),
+ instance.web._t.database.parameters.grouping = [3, 2, -1];
+ equal(instance.web.format_value(106500, {type: 'float'}),
'1,06,500.00');
- openerp.web._t.database.parameters.grouping = [1, 2, -1];
- equal(openerp.web.format_value(106500, {type: 'float'}),
+ instance.web._t.database.parameters.grouping = [1, 2, -1];
+ equal(instance.web.format_value(106500, {type: 'float'}),
'106,50,0.00');
- _.extend(openerp.web._t.database.parameters, {
+ _.extend(instance.web._t.database.parameters, {
grouping: [3, 0],
decimal_point: ',',
thousands_sep: '.'
});
- equal(openerp.web.format_value(6000, {type: 'float'}),
+ equal(instance.web.format_value(6000, {type: 'float'}),
'6.000,00');
});
- module('custom-date-formats', {
- setup: function () {
- openerp = window.openerp.init([]);
- window.openerp.web.corelib(openerp);
- window.openerp.web.coresetup(openerp);
- window.openerp.web.dates(openerp);
- window.openerp.web.formats(openerp);
- }
- });
- test('format stripper', function () {
- strictEqual(openerp.web.strip_raw_chars('%a, %Y %b %d'), '%a, %Y %b %d');
- strictEqual(openerp.web.strip_raw_chars('%a, %Y.eko %bren %da'), '%a, %Y. %b %d');
- });
- test('ES date format', function () {
- openerp.web._t.database.parameters.date_format = '%a, %Y %b %d';
- var date = openerp.web.str_to_date("2009-05-04");
- strictEqual(openerp.web.format_value(date, {type:"date"}), 'Mon, 2009 May 04');
- strictEqual(openerp.web.parse_value('Mon, 2009 May 04', {type: 'date'}), '2009-05-04');
- });
- test('extended ES date format', function () {
- openerp.web._t.database.parameters.date_format = '%a, %Y.eko %bren %da';
- var date = openerp.web.str_to_date("2009-05-04");
- strictEqual(openerp.web.format_value(date, {type:"date"}), 'Mon, 2009. May 04');
- strictEqual(openerp.web.parse_value('Mon, 2009. May 04', {type: 'date'}), '2009-05-04');
+});
+openerp.testing.section('web-formats', {
+ dependencies: ['web.formats']
+}, function (test) {
+ test('format stripper', function (instance) {
+ strictEqual(instance.web.strip_raw_chars('%a, %Y %b %d'),
+ '%a, %Y %b %d');
+ strictEqual(instance.web.strip_raw_chars('%a, %Y.eko %bren %da'),
+ '%a, %Y. %b %d');
+ });
+ test('ES date format', function (instance) {
+ instance.web._t.database.parameters.date_format = '%a, %Y %b %d';
+ var date = instance.web.str_to_date("2009-05-04");
+ strictEqual(instance.web.format_value(date, {type:"date"}),
+ 'Mon, 2009 May 04');
+ strictEqual(instance.web.parse_value('Mon, 2009 May 04', {type: 'date'}),
+ '2009-05-04');
+ });
+ test('extended ES date format', function (instance) {
+ instance.web._t.database.parameters.date_format = '%a, %Y.eko %bren %da';
+ var date = instance.web.str_to_date("2009-05-04");
+ strictEqual(instance.web.format_value(date, {type:"date"}),
+ 'Mon, 2009. May 04');
+ strictEqual(instance.web.parse_value('Mon, 2009. May 04', {type: 'date'}),
+ '2009-05-04');
});
});
diff --git a/addons/web/static/test/list-editable.js b/addons/web/static/test/list-editable.js
index 459a6df397a..b02e8abed96 100644
--- a/addons/web/static/test/list-editable.js
+++ b/addons/web/static/test/list-editable.js
@@ -1,16 +1,13 @@
-$(document).ready(function () {
- var $fix = $('#qunit-fixture');
-
- var instance;
- var baseSetup = function () {
- instance = openerp.testing.instanceFor('list_editable');
-
- openerp.testing.loadTemplate(instance);
-
- openerp.testing.mockifyRPC(instance);
- };
-
-
+openerp.testing.section('editor', {
+ dependencies: ['web.list_editable'],
+ rpc: 'mock',
+ templates: true,
+ setup: function (instance, $s, mock) {
+ mock('test.model:create', function () {
+ return 42;
+ });
+ }
+}, function (test) {
/**
*
* @param {String} name
@@ -30,7 +27,7 @@ $(document).ready(function () {
}
/**
- * @param {Array} fields
+ * @param {Array} [fields]
* @return {Object}
*/
function makeFormView(fields) {
@@ -67,46 +64,37 @@ $(document).ready(function () {
};
}
- module('editor', {
- setup: baseSetup
- });
- asyncTest('base-state', 2, function () {
+ test('base-state', {asserts: 2}, function (instance, $fix) {
var e = new instance.web.list.Editor({
dataset: {ids: []},
edition_view: function () {
return makeFormView();
}
});
- e.appendTo($fix)
- .always(start)
- .fail(function (error) { ok(false, error && error.message); })
+ return e.appendTo($fix)
.done(function () {
ok(!e.is_editing(), "should not be editing");
ok(e.form instanceof instance.web.FormView,
"should use default form type");
});
});
- asyncTest('toggle-edition-save', 4, function () {
- instance.session.responses['/web/dataset/call_kw:create'] = function () {
- return { result: 42 };
- };
- instance.session.responses['/web/dataset/call_kw:read'] = function () {
- return { result: [{
- id: 42,
- a: false,
- b: false,
- c: false
- }]};
- };
+ test('toggle-edition-save', {
+ asserts: 4,
+ setup: function (instance, $s, mock) {
+ mock('test.model:read', function () {
+ return [{id: 42, a: false, b: false, c: false}];
+ });
+ }
+ }, function (instance, $fix) {
var e = new instance.web.list.Editor({
- dataset: new instance.web.DataSetSearch(),
+ dataset: new instance.web.DataSetSearch(null, 'test.model'),
prepends_on_create: function () { return false; },
edition_view: function () {
return makeFormView([ field('a'), field('b'), field('c') ]);
}
});
var counter = 0;
- e.appendTo($fix)
+ return e.appendTo($fix)
.then(function () {
return e.edit({}, function () {
++counter;
@@ -117,26 +105,21 @@ $(document).ready(function () {
equal(counter, 3, "should have configured all fields");
return e.save();
})
- .always(start)
- .fail(function (error) { ok(false, error && error.message); })
.done(function (record) {
ok(!e.is_editing(), "should have stopped editing");
equal(record.id, 42, "should have newly created id");
})
});
- asyncTest('toggle-edition-cancel', 2, function () {
- instance.session.responses['/web/dataset/call_kw:create'] = function () {
- return { result: 42 };
- };
+ test('toggle-edition-cancel', { asserts: 2 }, function (instance, $fix) {
var e = new instance.web.list.Editor({
- dataset: new instance.web.DataSetSearch(),
+ dataset: new instance.web.DataSetSearch(null, 'test.model'),
prepends_on_create: function () { return false; },
edition_view: function () {
return makeFormView([ field('a'), field('b'), field('c') ]);
}
});
var counter = 0;
- e.appendTo($fix)
+ return e.appendTo($fix)
.then(function () {
return e.edit({}, function () {
++counter;
@@ -145,22 +128,20 @@ $(document).ready(function () {
.then(function (form) {
return e.cancel();
})
- .always(start)
- .fail(function (error) { ok(false, error && error.message); })
.done(function (record) {
ok(!e.is_editing(), "should have stopped editing");
ok(!record.id, "should have no id");
})
});
- asyncTest('toggle-save-required', 2, function () {
- instance.session.responses['/web/dataset/call_kw:create'] = function () {
- return { result: 42 };
- };
+ test('toggle-save-required', {
+ asserts: 2,
+ fail_on_rejection: false
+ }, function (instance, $fix) {
var e = new instance.web.list.Editor({
do_warn: function () {
warnings++;
},
- dataset: new instance.web.DataSetSearch(),
+ dataset: new instance.web.DataSetSearch(null, 'test.model'),
prepends_on_create: function () { return false; },
edition_view: function () {
return makeFormView([
@@ -169,7 +150,7 @@ $(document).ready(function () {
});
var counter = 0;
var warnings = 0;
- e.appendTo($fix)
+ return e.appendTo($fix)
.then(function () {
return e.edit({}, function () {
++counter;
@@ -178,78 +159,73 @@ $(document).ready(function () {
.then(function (form) {
return e.save();
})
- .always(start)
.done(function () { ok(false, "cancel should not succeed"); })
.fail(function () {
equal(warnings, 1, "should have been warned");
ok(e.is_editing(), "should have kept editing");
- })
+ });
});
-
- module('list-edition', {
- setup: function () {
- baseSetup();
-
- var records = {};
- _.extend(instance.session.responses, {
- '/web/view/load': function () {
- return {result: {
- type: 'tree',
- fields: {
- a: {type: 'char', string: "A"},
- b: {type: 'char', string: "B"},
- c: {type: 'char', string: "C"}
- },
- arch: {
- tag: 'tree',
- attrs: {},
- children: [
- {tag: 'field', attrs: {name: 'a'}},
- {tag: 'field', attrs: {name: 'b'}},
- {tag: 'field', attrs: {name: 'c'}}
- ]
- }
- }};
- },
- '/web/dataset/call_kw:create': function (params) {
- records[42] = _.extend({}, params.params.args[0]);
- return {result: 42};
+});
+openerp.testing.section('list.edition', {
+ dependencies: ['web.list_editable'],
+ rpc: 'mock',
+ templates: true,
+ setup: function (instance, $s, mock) {
+ var records = {};
+ mock('demo:create', function (args) {
+ records[42] = _.extend({}, args[0]);
+ return 42;
+ });
+ mock('demo:read', function (args) {
+ var id = args[0][0];
+ if (id in records) {
+ return [records[id]];
+ }
+ return [];
+ });
+ mock('/web/view/load', function () {
+ return {
+ type: 'tree',
+ fields: {
+ a: {type: 'char', string: "A"},
+ b: {type: 'char', string: "B"},
+ c: {type: 'char', string: "C"}
},
- '/web/dataset/call_kw:read': function (params) {
- var id = params.params.args[0][0];
- if (id in records) {
- return {result: [records[id]]};
- }
- return {result: []};
+ arch: {
+ tag: 'tree',
+ attrs: {},
+ children: [
+ {tag: 'field', attrs: {name: 'a'}},
+ {tag: 'field', attrs: {name: 'b'}},
+ {tag: 'field', attrs: {name: 'c'}}
+ ]
}
- })
- }
- });
- asyncTest('newrecord', 6, function () {
+ };
+ });
+ }
+}, function (test) {
+ test('newrecord', {asserts: 6}, function (instance, $fix, mock) {
var got_defaults = false;
- instance.session.responses['/web/dataset/call_kw:default_get'] = function (params) {
- var fields = params.params.args[0];
+ mock('demo:default_get', function (args) {
+ var fields = args[0];
deepEqual(
fields, ['a', 'b', 'c'],
"should ask defaults for all fields");
got_defaults = true;
- return {result: {
- a: "qux",
- b: "quux"
- }};
- };
+ return { a: "qux", b: "quux" };
+ });
var ds = new instance.web.DataSetStatic(null, 'demo', null, [1]);
var l = new instance.web.ListView({}, ds, false, {editable: 'top'});
- l.appendTo($fix)
+ return l.appendTo($fix)
.then(l.proxy('reload_content'))
.then(function () {
return l.start_edition();
})
- .always(start)
.then(function () {
ok(got_defaults, "should have fetched default values for form");
+
return l.save_edition();
})
.then(function (result) {
@@ -260,45 +236,39 @@ $(document).ready(function () {
"should have used default values");
ok(!result.record.get('c'),
"should have no value if there was no default");
- })
- .fail(function (e) { ok(false, e && e.message || e); });
+ });
});
-
- module('list-edition-events', {
- setup: function () {
- baseSetup();
- _.extend(instance.session.responses, {
- '/web/view/load': function () {
- return {result: {
- type: 'tree',
- fields: {
- a: {type: 'char', string: "A"},
- b: {type: 'char', string: "B"},
- c: {type: 'char', string: "C"}
- },
- arch: {
- tag: 'tree',
- attrs: {},
- children: [
- {tag: 'field', attrs: {name: 'a'}},
- {tag: 'field', attrs: {name: 'b'}},
- {tag: 'field', attrs: {name: 'c'}}
- ]
- }
- }};
+});
+openerp.testing.section('list.edition.events', {
+ dependencies: ['web.list_editable'],
+ rpc: 'mock',
+ templates: true,
+ setup: function (instance, $s, mock) {
+ mock('demo:read', function () {
+ return [{ id: 1, a: 'foo', b: 'bar', c: 'baz' }];
+ });
+ mock('/web/view/load', function () {
+ return {
+ type: 'tree',
+ fields: {
+ a: {type: 'char', string: "A"},
+ b: {type: 'char', string: "B"},
+ c: {type: 'char', string: "C"}
},
- '/web/dataset/call_kw:read': function (params) {
- return {result: [{
- id: 1,
- a: 'foo',
- b: 'bar',
- c: 'baz'
- }]};
+ arch: {
+ tag: 'tree',
+ attrs: {},
+ children: [
+ {tag: 'field', attrs: {name: 'a'}},
+ {tag: 'field', attrs: {name: 'b'}},
+ {tag: 'field', attrs: {name: 'c'}}
+ ]
}
- });
- }
- });
- asyncTest('edition events', 4, function () {
+ };
+ });
+ }
+}, function (test) {
+ test('edition events', {asserts: 4}, function (instance, $fix) {
var ds = new instance.web.DataSetStatic(null, 'demo', null, [1]);
var o = {
counter: 0,
@@ -306,9 +276,8 @@ $(document).ready(function () {
};
var l = new instance.web.ListView({}, ds, false, {editable: 'top'});
l.on('edit:before edit:after', o, o.onEvent);
- l.appendTo($fix)
+ return l.appendTo($fix)
.then(l.proxy('reload_content'))
- .always(start)
.then(function () {
ok(l.options.editable, "should be editable");
equal(o.counter, 0, "should have seen no event yet");
@@ -317,11 +286,10 @@ $(document).ready(function () {
.then(function () {
ok(l.editor.is_editing(), "should be editing");
equal(o.counter, 2, "should have seen two edition events");
- })
- .fail(function (e) { ok(false, e && e.message); });
+ });
});
- asyncTest('edition events: cancelling', 3, function () {
+ test('edition events: cancelling', {asserts: 3}, function (instance, $fix) {
var edit_after = false;
var ds = new instance.web.DataSetStatic(null, 'demo', null, [1]);
var l = new instance.web.ListView({}, ds, false, {editable: 'top'});
@@ -331,9 +299,8 @@ $(document).ready(function () {
l.on('edit:after', {}, function () {
edit_after = true;
});
- l.appendTo($fix)
+ return l.appendTo($fix)
.then(l.proxy('reload_content'))
- .always(start)
.then(function () {
ok(l.options.editable, "should be editable");
return l.start_edition();
@@ -343,7 +310,71 @@ $(document).ready(function () {
ok(!l.editor.is_editing(), "should not be editing");
ok(!edit_after, "should not have fired the edit:after event");
return $.when();
- })
- .fail(function (e) { ok(false, e && e.message || e); });
+ });
+ });
+});
+
+openerp.testing.section('list.edition.onwrite', {
+ dependencies: ['web.list_editable'],
+ rpc: 'mock',
+ templates: true,
+}, function (test) {
+ test('record-to-read', {asserts: 4}, function (instance, $fix, mock) {
+ mock('/web/view/load', function () {
+ return {
+ type: 'tree',
+ fields: {
+ a: {type: 'char', string: "A"}
+ },
+ arch: {
+ tag: 'tree',
+ attrs: { on_write: 'on_write', colors: 'red:a == "foo"' },
+ children: [
+ {tag: 'field', attrs: {name: 'a'}}
+ ]
+ }
+ };
+ });
+ mock('demo:read', function (args, kwargs) {
+ if (_.isEmpty(args[0])) {
+ return [];
+ } else if (_.isEqual(args[0], [1])) {
+ return [
+ {id: 1, a: 'some value'}
+ ];
+ } else if (_.isEqual(args[0], [42])) {
+ return [ {id: 42, a: 'foo'} ];
+ }
+ throw new Error(JSON.stringify(_.toArray(arguments)));
+ });
+ mock('demo:default_get', function () { return {}; });
+ mock('demo:create', function () { return 1; });
+ mock('demo:on_write', function () { return [42]; });
+
+ var ds = new instance.web.DataSetStatic(null, 'demo', null, []);
+ var l = new instance.web.ListView({}, ds, false, {editable: 'top'});
+ return l.appendTo($fix)
+ .then(l.proxy('reload_content'))
+ .then(function () {
+ return l.start_edition();
+ })
+ .then(function () {
+ $fix.find('.oe_form_field input').val("some value").change();
+ })
+ .then(function () {
+ return l.save_edition();
+ })
+ .then(function () {
+ strictEqual(ds.ids.length, 2,
+ 'should have id of created + on_write');
+ strictEqual(l.records.length, 2,
+ 'should have record of created + on_write');
+ strictEqual(
+ $fix.find('tbody tr:eq(1)').css('color'), 'rgb(255, 0, 0)',
+ 'shoud have color applied');
+ strictEqual(
+ $fix.find('tbody tr:eq(2)').css('color'), 'rgb(0, 0, 0)',
+ 'should have default color applied');
+ });
});
});
diff --git a/addons/web/static/test/list-utils.js b/addons/web/static/test/list-utils.js
index 6200ba788ce..84661138315 100644
--- a/addons/web/static/test/list-utils.js
+++ b/addons/web/static/test/list-utils.js
@@ -1,45 +1,34 @@
-$(document).ready(function () {
- var openerp,
- create = function (o) {
- if (typeof Object.create === 'function') {
- return Object.create(o);
- }
- function Cls() {}
- Cls.prototype = o;
- return new Cls;
- };
- module('list-events', {
- setup: function () {
- openerp = window.openerp.init([]);
- window.openerp.web.corelib(openerp);
- window.openerp.web.coresetup(openerp);
- window.openerp.web.chrome(openerp);
- // views loader stuff
- window.openerp.web.data(openerp);
- window.openerp.web.views(openerp);
- window.openerp.web.list(openerp);
+openerp.testing.section('list.events', {
+ dependencies: ['web.list']
+}, function (test) {
+ var create = function (o) {
+ if (typeof Object.create === 'function') {
+ return Object.create(o);
}
- });
- test('Simple event triggering', function () {
- var e = create(openerp.web.list.Events), passed = false;
+ function Cls() {}
+ Cls.prototype = o;
+ return new Cls;
+ };
+ test('Simple event triggering', function (instance) {
+ var e = create(instance.web.list.Events), passed = false;
e.bind('foo', function () { passed = true; });
e.trigger('foo');
ok(passed);
});
- test('Bind all', function () {
- var e = create(openerp.web.list.Events), event = null;
+ test('Bind all', function (instance) {
+ var e = create(instance.web.list.Events), event = null;
e.bind(null, function (ev) { event = ev; });
e.trigger('foo');
strictEqual(event, 'foo');
});
- test('Propagate trigger params', function () {
- var e = create(openerp.web.list.Events), p = false;
+ test('Propagate trigger params', function (instance) {
+ var e = create(instance.web.list.Events), p = false;
e.bind(null, function (_, param) { p = param });
e.trigger('foo', true);
strictEqual(p, true)
});
- test('Bind multiple callbacks', function () {
- var e = create(openerp.web.list.Events), count;
+ test('Bind multiple callbacks', function (instance) {
+ var e = create(instance.web.list.Events), count;
e.bind('foo', function () { count++; })
.bind('bar', function () { count++; })
.bind(null, function () { count++; })
@@ -59,20 +48,20 @@ $(document).ready(function () {
e.trigger('baz');
strictEqual(count, 3);
});
- test('Mixin events', function () {
- var cls = openerp.web.Class.extend({
+ test('Mixin events', function (instance) {
+ var cls = instance.web.Class.extend({
method: function () { this.trigger('e'); }
});
- cls.include(openerp.web.list.Events);
- var instance = new cls, triggered = false;
+ cls.include(instance.web.list.Events);
+ var i = new cls, triggered = false;
- instance.bind('e', function () { triggered = true; });
- instance.method();
+ i.bind('e', function () { triggered = true; });
+ i.method();
ok(triggered);
});
- test('Unbind all handlers', function () {
- var e = create(openerp.web.list.Events), passed = 0;
+ test('Unbind all handlers', function (instance) {
+ var e = create(instance.web.list.Events), passed = 0;
e.bind('foo', function () { passed++; });
e.trigger('foo');
strictEqual(passed, 1);
@@ -80,8 +69,8 @@ $(document).ready(function () {
e.trigger('foo');
strictEqual(passed, 1);
});
- test('Unbind one handler', function () {
- var e = create(openerp.web.list.Events), p1 = 0, p2 = 0,
+ test('Unbind one handler', function (instance) {
+ var e = create(instance.web.list.Events), p1 = 0, p2 = 0,
h1 = function () { p1++; }, h2 = function () { p2++; };
e.bind('foo', h1);
e.bind('foo', h2);
@@ -93,29 +82,20 @@ $(document).ready(function () {
strictEqual(p1, 1);
strictEqual(p2, 2);
});
-
- module('list-records', {
- setup: function () {
- openerp = window.openerp.init([]);
- window.openerp.web.corelib(openerp);
- window.openerp.web.coresetup(openerp);
- window.openerp.web.chrome(openerp);
- // views loader stuff
- window.openerp.web.data(openerp);
- window.openerp.web.views(openerp);
- window.openerp.web.list(openerp);
- }
- });
- test('Basic record initialization', function () {
- var r = new openerp.web.list.Record({qux: 3});
+});
+openerp.testing.section('list.records', {
+ dependencies: ['web.list']
+}, function (test) {
+ test('Basic record initialization', function (instance) {
+ var r = new instance.web.list.Record({qux: 3});
r.set('foo', 1);
r.set('bar', 2);
strictEqual(r.get('foo'), 1);
strictEqual(r.get('bar'), 2);
strictEqual(r.get('qux'), 3);
});
- test('Change all the things', function () {
- var r = new openerp.web.list.Record(), changed = false, field;
+ test('Change all the things', function (instance) {
+ var r = new instance.web.list.Record(), changed = false, field;
r.bind('change', function () { changed = true; });
r.bind(null, function (e) { field = field || e.split(':')[1]});
r.set('foo', 1);
@@ -123,8 +103,8 @@ $(document).ready(function () {
ok(changed);
strictEqual(field, 'foo');
});
- test('Change single field', function () {
- var r = new openerp.web.list.Record(), changed = 0;
+ test('Change single field', function (instance) {
+ var r = new instance.web.list.Record(), changed = 0;
r.bind('change:foo', function () { changed++; });
r.set('foo', 1);
r.set('bar', 1);
@@ -132,21 +112,12 @@ $(document).ready(function () {
strictEqual(r.get('bar'), 1);
strictEqual(changed, 1);
});
-
- module('list-collections', {
- setup: function () {
- openerp = window.openerp.init([]);
- window.openerp.web.corelib(openerp);
- window.openerp.web.coresetup(openerp);
- window.openerp.web.chrome(openerp);
- // views loader stuff
- window.openerp.web.data(openerp);
- window.openerp.web.views(openerp);
- window.openerp.web.list(openerp);
- }
- });
- test('degenerate-fetch', function () {
- var c = new openerp.web.list.Collection();
+});
+openerp.testing.section('list.collections', {
+ dependencies: ['web.list']
+}, function (test) {
+ test('degenerate-fetch', function (instance) {
+ var c = new instance.web.list.Collection();
strictEqual(c.length, 0);
c.add({id: 1, value: 2});
c.add({id: 2, value: 3});
@@ -155,16 +126,16 @@ $(document).ready(function () {
strictEqual(c.length, 4);
var r = c.at(2), r2 = c.get(1);
- ok(r instanceof openerp.web.list.Record);
+ ok(r instanceof instance.web.list.Record);
strictEqual(r.get('id'), 3);
strictEqual(r.get('value'), 5);
- ok(r2 instanceof openerp.web.list.Record);
+ ok(r2 instanceof instance.web.list.Record);
strictEqual(r2.get('id'), 1);
strictEqual(r2.get('value'), 2);
});
- test('degenerate-indexed-add', function () {
- var c = new openerp.web.list.Collection([
+ test('degenerate-indexed-add', function (instance) {
+ var c = new instance.web.list.Collection([
{id: 1, value: 5},
{id: 2, value: 10},
{id: 3, value: 20}
@@ -175,8 +146,8 @@ $(document).ready(function () {
strictEqual(c.at(1).get('value'), 55);
strictEqual(c.at(3).get('value'), 20);
});
- test('degenerate-remove', function () {
- var c = new openerp.web.list.Collection([
+ test('degenerate-remove', function (instance) {
+ var c = new instance.web.list.Collection([
{id: 1, value: 5},
{id: 2, value: 10},
{id: 3, value: 20}
@@ -188,9 +159,9 @@ $(document).ready(function () {
equal(c.get(2), undefined);
strictEqual(c.at(1).get('value'), 20);
});
- test('degenerate-remove-bound', function () {
+ test('degenerate-remove-bound', function (instance) {
var changed = false,
- c = new openerp.web.list.Collection([ {id: 1, value: 5} ]);
+ c = new instance.web.list.Collection([ {id: 1, value: 5} ]);
c.bind('change', function () { changed = true; });
var record = c.get(1);
c.remove(record);
@@ -198,8 +169,8 @@ $(document).ready(function () {
ok(!changed, 'removed records should not trigger events in their ' +
'parent collection');
});
- test('degenerate-reset', function () {
- var event, obj, c = new openerp.web.list.Collection([
+ test('degenerate-reset', function (instance) {
+ var event, obj, c = new instance.web.list.Collection([
{id: 1, value: 5},
{id: 2, value: 10},
{id: 3, value: 20}
@@ -218,9 +189,9 @@ $(document).ready(function () {
strictEqual(c.length, 1);
strictEqual(c.get(42).get('value'), 55);
});
- test('degenerate-reset-bound', function () {
+ test('degenerate-reset-bound', function (instance) {
var changed = false,
- c = new openerp.web.list.Collection([ {id: 1, value: 5} ]);
+ c = new instance.web.list.Collection([ {id: 1, value: 5} ]);
c.bind('change', function () { changed = true; });
var record = c.get(1);
c.reset();
@@ -229,9 +200,9 @@ $(document).ready(function () {
'parent collection');
});
- test('degenerate-propagations', function () {
+ test('degenerate-propagations', function (instance) {
var values = [];
- var c = new openerp.web.list.Collection([
+ var c = new instance.web.list.Collection([
{id: 1, value: 5},
{id: 2, value: 10},
{id: 3, value: 20}
@@ -244,8 +215,8 @@ $(document).ready(function () {
c.get(3).set('value', 21);
deepEqual(values, [6, 11, 21]);
});
- test('BTree', function () {
- var root = new openerp.web.list.Collection(),
+ test('BTree', function (instance) {
+ var root = new instance.web.list.Collection(),
c = root.proxy('admin'),
total = 0;
c.add({id: 1, name: "Administrator", login: 'admin'});
@@ -260,8 +231,8 @@ $(document).ready(function () {
c.at(1).set('wealth', 5);
strictEqual(total, 47);
});
- test('degenerate-successor', function () {
- var root = new openerp.web.list.Collection([
+ test('degenerate-successor', function (instance) {
+ var root = new instance.web.list.Collection([
{id: 1, value: 1},
{id: 2, value: 2},
{id: 3, value: 3},
@@ -282,8 +253,8 @@ $(document).ready(function () {
root.at(3).attributes,
"wraparound should have no effect if not succ(last_record)");
});
- test('successor', function () {
- var root = new openerp.web.list.Collection();
+ test('successor', function (instance) {
+ var root = new instance.web.list.Collection();
root.proxy('first').add([{id: 1, value: 1}, {id: 2, value: 2}]);
root.proxy('second').add([{id: 3, value: 3}, {id: 4, value: 5}]);
root.proxy('third').add([{id: 5, value: 8}, {id: 6, value: 13}]);
@@ -298,8 +269,8 @@ $(document).ready(function () {
root.get(3).attributes,
"should wraparound within a collection");
});
- test('degenerate-predecessor', function () {
- var root = new openerp.web.list.Collection([
+ test('degenerate-predecessor', function (instance) {
+ var root = new instance.web.list.Collection([
{id: 1, value: 1},
{id: 2, value: 2},
{id: 3, value: 3},
@@ -320,8 +291,8 @@ $(document).ready(function () {
root.at(0).attributes,
"wraparound should have no effect if not pred(first_record)");
});
- test('predecessor', function () {
- var root = new openerp.web.list.Collection();
+ test('predecessor', function (instance) {
+ var root = new instance.web.list.Collection();
root.proxy('first').add([{id: 1, value: 1}, {id: 2, value: 2}]);
root.proxy('second').add([{id: 3, value: 3}, {id: 4, value: 5}]);
root.proxy('third').add([{id: 5, value: 8}, {id: 6, value: 13}]);
@@ -336,21 +307,12 @@ $(document).ready(function () {
root.get(4).attributes,
"should wraparound within a collection");
});
-
- module('list-hofs', {
- setup: function () {
- openerp = window.openerp.init([]);
- window.openerp.web.corelib(openerp);
- window.openerp.web.coresetup(openerp);
- window.openerp.web.chrome(openerp);
- // views loader stuff
- window.openerp.web.data(openerp);
- window.openerp.web.views(openerp);
- window.openerp.web.list(openerp);
- }
- });
- test('each, degenerate', function () {
- var c = new openerp.web.list.Collection([
+});
+openerp.testing.section('list.collections.hom', {
+ dependencies: ['web.list']
+}, function (test) {
+ test('each, degenerate', function (instance) {
+ var c = new instance.web.list.Collection([
{id: 1, value: 5},
{id: 2, value: 10},
{id: 3, value: 20}
@@ -362,8 +324,8 @@ $(document).ready(function () {
ids, [1, 2, 3],
'degenerate collections should be iterated in record order');
});
- test('each, deep', function () {
- var root = new openerp.web.list.Collection(),
+ test('each, deep', function (instance) {
+ var root = new instance.web.list.Collection(),
ids = [];
root.proxy('foo').add([
{id: 1, value: 5},
@@ -382,8 +344,8 @@ $(document).ready(function () {
ids, [1, 2, 3, 10, 20, 30],
'tree collections should be deeply iterated');
});
- test('map, degenerate', function () {
- var c = new openerp.web.list.Collection([
+ test('map, degenerate', function (instance) {
+ var c = new instance.web.list.Collection([
{id: 1, value: 5},
{id: 2, value: 10},
{id: 3, value: 20}
@@ -395,8 +357,8 @@ $(document).ready(function () {
ids, [1, 2, 3],
'degenerate collections should be iterated in record order');
});
- test('map, deep', function () {
- var root = new openerp.web.list.Collection();
+ test('map, deep', function (instance) {
+ var root = new instance.web.list.Collection();
root.proxy('foo').add([
{id: 1, value: 5},
{id: 2, value: 10},
@@ -414,29 +376,20 @@ $(document).ready(function () {
ids, [1, 2, 3, 10, 20, 30],
'tree collections should be deeply iterated');
});
-
- module("list-weirds", {
- setup: function () {
- openerp = window.openerp.init([]);
- window.openerp.web.corelib(openerp);
- window.openerp.web.coresetup(openerp);
- window.openerp.web.chrome(openerp);
- // views loader stuff
- window.openerp.web.data(openerp);
- window.openerp.web.views(openerp);
- window.openerp.web.list(openerp);
- }
- });
- test('set-from-noid', function () {
- var root = new openerp.web.list.Collection();
+});
+openerp.testing.section('list.collection.weirdoes', {
+ dependencies: ['web.list']
+}, function (test) {
+ test('set-from-noid', function (instance) {
+ var root = new instance.web.list.Collection();
root.add({v: 3});
root.at(0).set('id', 42);
var record = root.get(42);
equal(root.length, 1);
equal(record.get('v'), 3, "should have fetched the original record");
});
- test('set-from-previd', function () {
- var root = new openerp.web.list.Collection();
+ test('set-from-previd', function (instance) {
+ var root = new instance.web.list.Collection();
root.add({id: 1, v: 2});
root.get(1).set('id', 42);
var record = root.get(42);
diff --git a/addons/web/static/test/list.js b/addons/web/static/test/list.js
new file mode 100644
index 00000000000..22c33ef104a
--- /dev/null
+++ b/addons/web/static/test/list.js
@@ -0,0 +1,56 @@
+openerp.testing.section('list.buttons', {
+ dependencies: ['web.list', 'web.form'],
+ rpc: 'mock',
+ templates: true
+}, function (test) {
+ test('record-deletion', {asserts: 2}, function (instance, $fix, mock) {
+ mock('/web/view/load', function () {
+ return {
+ type: 'tree',
+ fields: {
+ a: {type: 'char', string: "A"}
+ },
+ arch: {
+ tag: 'tree',
+ attrs: { },
+ children: [
+ {tag: 'field', attrs: {name: 'a'}},
+ {tag: 'button', attrs: {type: 'object', name: 'foo'}}
+ ]
+ }
+ };
+ });
+ mock('demo:read', function (args, kwargs) {
+ if (_.isEqual(args[0], [1, 2, 3])) {
+ return [
+ {id: 1, a: 'foo'}, {id: 2, a: 'bar'}, {id: 3, a: 'baz'}
+ ];
+ } else if (_.isEqual(args[0], [2])) {
+ // button action virtually removed record
+ return [];
+ }
+ throw new Error(JSON.stringify(_.toArray(arguments)));
+ });
+ mock('/web/dataset/call_button', function () { return false; });
+ var ds = new instance.web.DataSetStatic(null, 'demo', null, [1, 2, 3]);
+ var l = new instance.web.ListView({
+ do_action: openerp.testing.noop
+ }, ds, false, {editable: 'top'});
+ return l.appendTo($fix)
+ .then(l.proxy('reload_content'))
+ .then(function () {
+ var d = $.Deferred();
+ l.records.bind('remove', function () {
+ d.resolve();
+ });
+ $fix.find('table tbody tr:eq(1) button').click();
+ return d.promise();
+ })
+ .then(function () {
+ strictEqual(l.records.length, 2,
+ "should have 2 records left");
+ strictEqual($fix.find('table tbody tr[data-id]').length, 2,
+ "should have 2 rows left");
+ });
+ });
+});
diff --git a/addons/web/static/test/mutex.js b/addons/web/static/test/mutex.js
new file mode 100644
index 00000000000..e88911b5b45
--- /dev/null
+++ b/addons/web/static/test/mutex.js
@@ -0,0 +1,60 @@
+openerp.testing.section('mutex', {
+ dependencies: ['web.coresetup'],
+ setup: function (instance) {
+ }
+}, function (test) {
+ test('simpleScheduling', function (instance) {
+ var m = new $.Mutex();
+ var def1 = $.Deferred();
+ var def2 = $.Deferred();
+ var p1 = m.exec(function() { return def1; });
+ var p2 = m.exec(function() { return def2; });
+ equal(p1.state(), "pending");
+ equal(p2.state(), "pending");
+ def1.resolve();
+ equal(p1.state(), "resolved");
+ equal(p2.state(), "pending");
+ def2.resolve();
+ equal(p1.state(), "resolved");
+ equal(p2.state(), "resolved");
+ });
+ test('simpleScheduling2', function (instance) {
+ var m = new $.Mutex();
+ var def1 = $.Deferred();
+ var def2 = $.Deferred();
+ var p1 = m.exec(function() { return def1; });
+ var p2 = m.exec(function() { return def2; });
+ equal(p1.state(), "pending");
+ equal(p2.state(), "pending");
+ def2.resolve();
+ equal(p1.state(), "pending");
+ equal(p2.state(), "pending");
+ def1.resolve();
+ equal(p1.state(), "resolved");
+ equal(p2.state(), "resolved");
+ });
+ test('reject', function (instance) {
+ var m = new $.Mutex();
+ var def1 = $.Deferred();
+ var def2 = $.Deferred();
+ var def3 = $.Deferred();
+ var p1 = m.exec(function() {return def1;});
+ var p2 = m.exec(function() {return def2;});
+ var p3 = m.exec(function() {return def3;});
+ equal(p1.state(), "pending");
+ equal(p2.state(), "pending");
+ equal(p3.state(), "pending");
+ def1.resolve();
+ equal(p1.state(), "resolved");
+ equal(p2.state(), "pending");
+ equal(p3.state(), "pending");
+ def2.reject();
+ equal(p1.state(), "resolved");
+ equal(p2.state(), "rejected");
+ equal(p3.state(), "pending");
+ def3.resolve();
+ equal(p1.state(), "resolved");
+ equal(p2.state(), "rejected");
+ equal(p3.state(), "resolved");
+ });
+});
diff --git a/addons/web/static/test/registry.js b/addons/web/static/test/registry.js
index ca689a351e0..2ad69a541bc 100644
--- a/addons/web/static/test/registry.js
+++ b/addons/web/static/test/registry.js
@@ -1,58 +1,55 @@
-$(document).ready(function () {
- var openerp;
- module('Registry', {
- setup: function () {
- openerp = window.openerp.init([]);
- window.openerp.web.corelib(openerp);
- openerp.web.Foo = {};
- openerp.web.Bar = {};
- openerp.web.Foo2 = {};
- }
- });
- test('key set', function () {
- var reg = new openerp.web.Registry();
+openerp.testing.section('registry', {
+ dependencies: ['web.corelib'],
+ setup: function (instance) {
+ instance.web.Foo = {};
+ instance.web.Bar = {};
+ instance.web.Foo2 = {};
+ }
+}, function (test) {
+ test('key set', function (instance) {
+ var reg = new instance.web.Registry();
- reg.add('foo', 'openerp.web.Foo')
- .add('bar', 'openerp.web.Bar');
- strictEqual(reg.get_object('bar'), openerp.web.Bar);
+ reg.add('foo', 'instance.web.Foo')
+ .add('bar', 'instance.web.Bar');
+ strictEqual(reg.get_object('bar'), instance.web.Bar);
});
- test('extension', function () {
- var reg = new openerp.web.Registry({
- foo: 'openerp.web.Foo',
- bar: 'openerp.web.Bar'
+ test('extension', function (instance) {
+ var reg = new instance.web.Registry({
+ foo: 'instance.web.Foo',
+ bar: 'instance.web.Bar'
});
- var reg2 = reg.extend({ 'foo': 'openerp.web.Foo2' });
- strictEqual(reg.get_object('foo'), openerp.web.Foo);
- strictEqual(reg2.get_object('foo'), openerp.web.Foo2);
+ var reg2 = reg.extend({ 'foo': 'instance.web.Foo2' });
+ strictEqual(reg.get_object('foo'), instance.web.Foo);
+ strictEqual(reg2.get_object('foo'), instance.web.Foo2);
});
- test('remain-linked', function () {
- var reg = new openerp.web.Registry({
- foo: 'openerp.web.Foo',
- bar: 'openerp.web.Bar'
+ test('remain-linked', function (instance) {
+ var reg = new instance.web.Registry({
+ foo: 'instance.web.Foo',
+ bar: 'instance.web.Bar'
});
var reg2 = reg.extend();
- reg.add('foo2', 'openerp.web.Foo2');
- strictEqual(reg.get_object('foo2'), openerp.web.Foo2);
- strictEqual(reg2.get_object('foo2'), openerp.web.Foo2);
+ reg.add('foo2', 'instance.web.Foo2');
+ strictEqual(reg.get_object('foo2'), instance.web.Foo2);
+ strictEqual(reg2.get_object('foo2'), instance.web.Foo2);
});
- test('multiget', function () {
- var reg = new openerp.web.Registry({
- foo: 'openerp.web.Foo',
- bar: 'openerp.web.Bar'
+ test('multiget', function (instance) {
+ var reg = new instance.web.Registry({
+ foo: 'instance.web.Foo',
+ bar: 'instance.web.Bar'
});
strictEqual(reg.get_any(['qux', 'grault', 'bar', 'foo']),
- openerp.web.Bar);
+ instance.web.Bar);
});
- test('extended-multiget', function () {
- var reg = new openerp.web.Registry({
- foo: 'openerp.web.Foo',
- bar: 'openerp.web.Bar'
+ test('extended-multiget', function (instance) {
+ var reg = new instance.web.Registry({
+ foo: 'instance.web.Foo',
+ bar: 'instance.web.Bar'
});
var reg2 = reg.extend();
strictEqual(reg2.get_any(['qux', 'grault', 'bar', 'foo']),
- openerp.web.Bar);
+ instance.web.Bar);
});
});
diff --git a/addons/web/static/test/rpc.js b/addons/web/static/test/rpc.js
index be7cbce9f41..85f562ef225 100644
--- a/addons/web/static/test/rpc.js
+++ b/addons/web/static/test/rpc.js
@@ -1,16 +1,8 @@
-$(document).ready(function () {
- var openerp;
-
- module('Misordered resolution management', {
- setup: function () {
- openerp = window.openerp.init([]);
- window.openerp.web.corelib(openerp);
- window.openerp.web.coresetup(openerp);
- window.openerp.web.data(openerp);
- }
- });
- test('Resolve all correctly ordered, sync', function () {
- var dm = new openerp.web.DropMisordered(), flag = false;
+openerp.testing.section('misordered resolution managemeng', {
+ dependencies: ['web.data']
+}, function (test) {
+ test('Resolve all correctly ordered, sync', function (instance) {
+ var dm = new instance.web.DropMisordered(), flag = false;
var d1 = $.Deferred(), d2 = $.Deferred(),
r1 = dm.add(d1), r2 = dm.add(d2);
@@ -23,8 +15,8 @@ $(document).ready(function () {
ok(flag);
});
- test("Don't resolve mis-ordered, sync", function () {
- var dm = new openerp.web.DropMisordered(),
+ test("Don't resolve mis-ordered, sync", function (instance) {
+ var dm = new instance.web.DropMisordered(),
done1 = false, done2 = false,
fail1 = false, fail2 = false;
@@ -44,8 +36,8 @@ $(document).ready(function () {
ok(done2);
ok(!fail2);
});
- test('Fail mis-ordered flag, sync', function () {
- var dm = new openerp.web.DropMisordered(true),
+ test('Fail mis-ordered flag, sync', function (instance) {
+ var dm = new instance.web.DropMisordered(true),
done1 = false, done2 = false,
fail1 = false, fail2 = false;
@@ -66,8 +58,8 @@ $(document).ready(function () {
ok(!fail2);
});
- asyncTest('Resolve all correctly ordered, async', 1, function () {
- var dm = new openerp.web.DropMisordered();
+ test('Resolve all correctly ordered, async', {asserts: 1}, function (instance) {
+ var dm = new instance.web.DropMisordered();
var d1 = $.Deferred(), d2 = $.Deferred(),
r1 = dm.add(d1), r2 = dm.add(d2);
@@ -75,13 +67,12 @@ $(document).ready(function () {
setTimeout(function () { d1.resolve(); }, 100);
setTimeout(function () { d2.resolve(); }, 200);
- $.when(r1, r2).done(function () {
- start();
+ return $.when(r1, r2).done(function () {
ok(true);
});
});
- asyncTest("Don't resolve mis-ordered, async", 4, function () {
- var dm = new openerp.web.DropMisordered(),
+ test("Don't resolve mis-ordered, async", {asserts: 4}, function (instance) {
+ var dm = new instance.web.DropMisordered(),
done1 = false, done2 = false,
fail1 = false, fail2 = false;
@@ -94,18 +85,20 @@ $(document).ready(function () {
setTimeout(function () { d1.resolve(); }, 200);
setTimeout(function () { d2.resolve(); }, 100);
+ var done = $.Deferred();
setTimeout(function () {
- start();
// d1 is in limbo
ok(!done1);
ok(!fail1);
// d2 is resolved
ok(done2);
ok(!fail2);
+ done.resolve();
}, 400);
+ return $.when(d1, d2, done);
});
- asyncTest('Fail mis-ordered flag, async', 4, function () {
- var dm = new openerp.web.DropMisordered(true),
+ test('Fail mis-ordered flag, async', {asserts: 4}, function (instance) {
+ var dm = new instance.web.DropMisordered(true),
done1 = false, done2 = false,
fail1 = false, fail2 = false;
@@ -118,6 +111,7 @@ $(document).ready(function () {
setTimeout(function () { d1.resolve(); }, 200);
setTimeout(function () { d2.resolve(); }, 100);
+ var done = $.Deferred();
setTimeout(function () {
start();
// d1 is failed
@@ -126,6 +120,8 @@ $(document).ready(function () {
// d2 is resolved
ok(done2);
ok(!fail2);
+ done.resolve();
}, 400);
+ return $.when(d1, d2, done)
});
});
diff --git a/addons/web/static/test/search.js b/addons/web/static/test/search.js
index 3ef4d6840e2..20534eff76a 100644
--- a/addons/web/static/test/search.js
+++ b/addons/web/static/test/search.js
@@ -1,11 +1,7 @@
-$(document).ready(function () {
- var instance;
- module('query', {
- setup: function () {
- instance = openerp.testing.instanceFor('search');
- }
- });
- test('Adding a facet to the query creates a facet and a value', function () {
+openerp.testing.section('query', {
+ dependencies: ['web.search']
+}, function (test) {
+ test('Adding a facet to the query creates a facet and a value', function (instance) {
var query = new instance.web.search.SearchQuery;
var field = {};
query.add({
@@ -19,7 +15,7 @@ $(document).ready(function () {
equal(facet.get('field'), field);
deepEqual(facet.get('values'), [{label: 'Value', value: 3}]);
});
- test('Adding two facets', function () {
+ test('Adding two facets', function (instance) {
var query = new instance.web.search.SearchQuery;
query.add([
{ category: 'Foo', field: {}, values: [{label: 'Value', value: 3}] },
@@ -30,7 +26,7 @@ $(document).ready(function () {
equal(query.at(0).values.length, 1);
equal(query.at(1).values.length, 1);
});
- test('If a facet already exists, add values to it', function () {
+ test('If a facet already exists, add values to it', function (instance) {
var query = new instance.web.search.SearchQuery;
var field = {};
query.add({category: 'A', field: field, values: [{label: 'V1', value: 0}]});
@@ -43,7 +39,7 @@ $(document).ready(function () {
{label: 'V2', value: 1}
]);
});
- test('Facet being implicitly changed should trigger change, not add', function () {
+ test('Facet being implicitly changed should trigger change, not add', function (instance) {
var query = new instance.web.search.SearchQuery;
var field = {}, added = false, changed = false;
query.add({category: 'A', field: field, values: [{label: 'V1', value: 0}]});
@@ -54,7 +50,7 @@ $(document).ready(function () {
ok(!added, "query.add adding values to a facet should not trigger an add");
ok(changed, "query.add adding values to a facet should not trigger a change");
});
- test('Toggling a facet, value which does not exist should add it', function () {
+ test('Toggling a facet, value which does not exist should add it', function (instance) {
var query = new instance.web.search.SearchQuery;
var field = {};
query.toggle({category: 'A', field: field, values: [{label: 'V1', value: 0}]});
@@ -65,7 +61,7 @@ $(document).ready(function () {
deepEqual(facet.get('values'), [{label: 'V1', value: 0}],
"Facet's value should match input");
});
- test('Toggling a facet which exists with a value which does not should add the value to the facet', function () {
+ test('Toggling a facet which exists with a value which does not should add the value to the facet', function (instance) {
var field = {};
var query = new instance.web.search.SearchQuery;
query.add({category: 'A', field: field, values: [{label: 'V1', value: 0}]});
@@ -79,7 +75,7 @@ $(document).ready(function () {
{label: 'V2', value: 1}
]);
});
- test('Toggling a facet which exists with a value which does as well should remove the value from the facet', function () {
+ test('Toggling a facet which exists with a value which does as well should remove the value from the facet', function (instance) {
var field = {};
var query = new instance.web.search.SearchQuery;
query.add({category: 'A', field: field, values: [{label: 'V1', value: 0}]});
@@ -94,7 +90,7 @@ $(document).ready(function () {
{label: 'V1', value: 0}
]);
});
- test('Toggling off the last value of a facet should remove the facet', function () {
+ test('Toggling off the last value of a facet should remove the facet', function (instance) {
var field = {};
var query = new instance.web.search.SearchQuery;
query.add({category: 'A', field: field, values: [{label: 'V1', value: 0}]});
@@ -103,7 +99,7 @@ $(document).ready(function () {
equal(query.length, 0, 'Should have removed the facet');
});
- test('Intermediate emptiness should not remove the facet', function () {
+ test('Intermediate emptiness should not remove the facet', function (instance) {
var field = {};
var query = new instance.web.search.SearchQuery;
query.add({category: 'A', field: field, values: [{label: 'V1', value: 0}]});
@@ -121,7 +117,7 @@ $(document).ready(function () {
]);
});
- test('Reseting with multiple facets should still work to load defaults', function () {
+ test('Reseting with multiple facets should still work to load defaults', function (instance) {
var query = new instance.web.search.SearchQuery;
var field = {};
query.reset([
@@ -135,78 +131,75 @@ $(document).ready(function () {
{label: 'V2', value: 1}
])
});
+});
- module('defaults', {
- setup: function () {
- instance = openerp.testing.instanceFor('search');
-
- openerp.testing.loadTemplate(instance);
-
- openerp.testing.mockifyRPC(instance);
- }
- });
-
- /**
- * Builds a basic search view with a single "dummy" field. The dummy
- * extends `instance.web.search.Field`, it does not add any (class)
- * attributes beyond what is provided through ``dummy_widget_attributes``.
- *
- * The view is returned un-started, it is the caller's role to start it
- * (or use DOM-insertion methods to start it indirectly).
- *
- * @param [dummy_widget_attributes={}]
- * @param [defaults={}]
- * @return {instance.web.SearchView}
- */
- function makeSearchView(dummy_widget_attributes, defaults) {
- instance.web.search.fields.add(
- 'dummy', 'instance.dummy.DummyWidget');
- instance.dummy = {};
- instance.dummy.DummyWidget = instance.web.search.Field.extend(
- dummy_widget_attributes || {});
- if (!('/web/searchview/load' in instance.session.responses)) {
- instance.session.responses['/web/searchview/load'] = function () {
- return {result: {fields_view: {
- type: 'search',
- fields: {
- dummy: {type: 'char', string: "Dummy"}
- },
- arch: {
- tag: 'search',
- attrs: {},
- children: [{
- tag: 'field',
- attrs: {
- name: 'dummy',
- widget: 'dummy'
- },
- children: []
- }]
- }
- }}};
- };
- }
- instance.session.responses['/web/searchview/get_filters'] = function () {
- return {result: []};
- };
- instance.session.responses['/web/searchview/fields_get'] = function () {
- return {result: {fields: {
- dummy: {type: 'char', string: 'Dummy'}
- }}};
+/**
+ * Builds a basic search view with a single "dummy" field. The dummy
+ * extends `instance.web.search.Field`, it does not add any (class)
+ * attributes beyond what is provided through ``dummy_widget_attributes``.
+ *
+ * The view is returned un-started, it is the caller's role to start it
+ * (or use DOM-insertion methods to start it indirectly).
+ *
+ * @param instance
+ * @param [dummy_widget_attributes={}]
+ * @param [defaults={}]
+ * @return {instance.web.SearchView}
+ */
+var makeSearchView = function (instance, dummy_widget_attributes, defaults) {
+ instance.web.search.fields.add(
+ 'dummy', 'instance.dummy.DummyWidget');
+ instance.dummy = {};
+ instance.dummy.DummyWidget = instance.web.search.Field.extend(
+ dummy_widget_attributes || {});
+ if (!('/web/searchview/load' in instance.session.responses)) {
+ instance.session.responses['/web/searchview/load'] = function () {
+ return {fields_view: {
+ type: 'search',
+ fields: {
+ dummy: {type: 'char', string: "Dummy"}
+ },
+ arch: {
+ tag: 'search',
+ attrs: {},
+ children: [{
+ tag: 'field',
+ attrs: {
+ name: 'dummy',
+ widget: 'dummy'
+ },
+ children: []
+ }]
+ }
+ }};
};
-
- var dataset = {model: 'dummy.model', get_context: function () { return {}; }};
- var view = new instance.web.SearchView(null, dataset, false, defaults);
- var self = this;
- view.on('invalid_search', self, function () {
- ok(false, JSON.stringify([].slice(arguments)));
- });
- return view;
}
- asyncTest('calling', 2, function () {
+ instance.session.responses['/web/searchview/get_filters'] = function () {
+ return [];
+ };
+ instance.session.responses['/web/searchview/fields_get'] = function () {
+ return {fields: {
+ dummy: {type: 'char', string: 'Dummy'}
+ }};
+ };
+
+ var dataset = {model: 'dummy.model', get_context: function () { return {}; }};
+ var view = new instance.web.SearchView(null, dataset, false, defaults);
+ var self = this;
+ view.on('invalid_search', self, function () {
+ ok(false, JSON.stringify([].slice(arguments)));
+ });
+ return view;
+};
+openerp.testing.section('defaults', {
+ dependencies: ['web.search'],
+ rpc: 'mock',
+ templates: true,
+}, function (test) {
+ test('calling', {asserts: 2}, function (instance, $s) {
var defaults_called = false;
- var view = makeSearchView({
+ var view = makeSearchView(instance, {
facet_for_defaults: function (defaults) {
defaults_called = true;
return $.when({
@@ -216,9 +209,7 @@ $(document).ready(function () {
});
}
}, {dummy: 42});
- view.appendTo($('#qunit-fixture'))
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ return view.appendTo($s)
.done(function () {
ok(defaults_called, "should have called defaults");
deepEqual(
@@ -227,7 +218,7 @@ $(document).ready(function () {
"should have generated a facet with the default value");
});
});
- asyncTest('FilterGroup', 3, function () {
+ test('FilterGroup', {asserts: 3}, function (instance) {
var view = {inputs: [], query: {on: function () {}}};
var filter_a = new instance.web.search.Filter(
{attrs: {name: 'a'}}, view);
@@ -235,9 +226,7 @@ $(document).ready(function () {
{attrs: {name: 'b'}}, view);
var group = new instance.web.search.FilterGroup(
[filter_a, filter_b], view);
- group.facet_for_defaults({a: true, b: true})
- .always(start)
- .fail(function (error) { ok(false, error && error.message); })
+ return group.facet_for_defaults({a: true, b: true})
.done(function (facet) {
var model = facet;
if (!(model instanceof instance.web.search.Facet)) {
@@ -249,13 +238,11 @@ $(document).ready(function () {
strictEqual(values.at(1).get('value'), filter_b);
});
});
- asyncTest('Field', 4, function () {
+ test('Field', {asserts: 4}, function (instance) {
var view = {inputs: []};
var f = new instance.web.search.Field(
{attrs: {string: 'Dummy', name: 'dummy'}}, {}, view);
- f.facet_for_defaults({dummy: 42})
- .always(start)
- .fail(function (error) { ok(false, error && error.message); })
+ return f.facet_for_defaults({dummy: 42})
.done(function (facet) {
var model = facet;
if (!(model instanceof instance.web.search.Facet)) {
@@ -275,15 +262,13 @@ $(document).ready(function () {
"facet value should match provided default");
});
});
- asyncTest('Selection: valid value', 4, function () {
+ test('Selection: valid value', {asserts: 4}, function (instance) {
var view = {inputs: []};
var f = new instance.web.search.SelectionField(
{attrs: {name: 'dummy', string: 'Dummy'}},
{selection: [[1, "Foo"], [2, "Bar"], [3, "Baz"], [4, "Qux"]]},
view);
- f.facet_for_defaults({dummy: 3})
- .always(start)
- .fail(function (error) { ok(false, error && error.message); })
+ return f.facet_for_defaults({dummy: 3})
.done(function (facet) {
var model = facet;
if (!(model instanceof instance.web.search.Facet)) {
@@ -303,36 +288,28 @@ $(document).ready(function () {
"facet value should match provided default's selection");
});
});
- asyncTest('Selection: invalid value', 1, function () {
+ test('Selection: invalid value', {asserts: 1}, function (instance) {
var view = {inputs: []};
var f = new instance.web.search.SelectionField(
{attrs: {name: 'dummy', string: 'Dummy'}},
{selection: [[1, "Foo"], [2, "Bar"], [3, "Baz"], [4, "Qux"]]},
view);
- f.facet_for_defaults({dummy: 42})
- .always(start)
- .fail(function (error) { ok(false, error && error.message); })
+ return f.facet_for_defaults({dummy: 42})
.done(function (facet) {
ok(!facet, "an invalid value should result in a not-facet");
});
});
- asyncTest("M2O default: value", 7, function () {
+ test("M2O default: value", {asserts: 5}, function (instance, $s, mock) {
var view = {inputs: []}, id = 4;
var f = new instance.web.search.ManyToOneField(
{attrs: {name: 'dummy', string: 'Dummy'}},
{relation: 'dummy.model.name'},
view);
- instance.session.responses['/web/dataset/call_kw'] = function (req) {
- equal(req.params.method, 'name_get',
- "m2o should resolve default id");
- equal(req.params.model, f.attrs.relation,
- "query model should match m2o relation");
- equal(req.params.args[0], id);
- return {result: [[id, "DumDumDum"]]};
- };
- f.facet_for_defaults({dummy: id})
- .always(start)
- .fail(function (error) { ok(false, error && error.message); })
+ mock('dummy.model.name:name_get', function (args) {
+ equal(args[0], id);
+ return [[id, "DumDumDum"]];
+ });
+ return f.facet_for_defaults({dummy: id})
.done(function (facet) {
var model = facet;
if (!(model instanceof instance.web.search.Facet)) {
@@ -352,34 +329,26 @@ $(document).ready(function () {
"facet value should match provided default's selection");
});
});
- asyncTest("M2O default: value", 1, function () {
+ test("M2O default: value", {asserts: 1}, function (instance, $s, mock) {
var view = {inputs: []}, id = 4;
var f = new instance.web.search.ManyToOneField(
{attrs: {name: 'dummy', string: 'Dummy'}},
{relation: 'dummy.model.name'},
view);
- instance.session.responses['/web/dataset/call_kw'] = function (req) {
- return {result: []};
- };
- f.facet_for_defaults({dummy: id})
- .always(start)
- .fail(function (error) { ok(false, error && error.message); })
+ mock('dummy.model.name:name_get', function () { return [] });
+ return f.facet_for_defaults({dummy: id})
.done(function (facet) {
ok(!facet, "an invalid m2o default should yield a non-facet");
});
});
-
- module('completions', {
- setup: function () {
- instance = openerp.testing.instanceFor('search');
-
- openerp.testing.loadTemplate(instance);
-
- openerp.testing.mockifyRPC(instance);
- }
- });
- asyncTest('calling', 4, function () {
- var view = makeSearchView({
+});
+openerp.testing.section('completions', {
+ dependencies: ['web.search'],
+ rpc: 'mock',
+ templates: true
+}, function (test) {
+ test('calling', {asserts: 4}, function (instance, $s) {
+ var view = makeSearchView(instance, {
complete: function () {
return $.when({
label: "Dummy",
@@ -391,10 +360,11 @@ $(document).ready(function () {
});
}
});
- view.appendTo($('#qunit-fixture'))
- .done(function () {
+ var done = $.Deferred();
+ view.appendTo($s)
+ .then(function () {
view.complete_global_search({term: "dum"}, function (completions) {
- start();
+ done.resolve();
equal(completions.length, 1, "should have a single completion");
var completion = completions[0];
equal(completion.label, "Dummy",
@@ -405,9 +375,10 @@ $(document).ready(function () {
[{label: 'dummy', value: 42}],
"should have provided values");
});
- });
+ }).fail(function () { done.reject.apply(done, arguments); });
+ return done;
});
- asyncTest('facet selection', 2, function () {
+ test('facet selection', {asserts: 2}, function (instance, $s) {
var completion = {
label: "Dummy",
facet: {
@@ -421,10 +392,8 @@ $(document).ready(function () {
}
};
- var view = makeSearchView({});
- view.appendTo($('#qunit-fixture'))
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ var view = makeSearchView(instance);
+ return view.appendTo($s)
.done(function () {
view.select_completion(
{preventDefault: function () {}},
@@ -436,7 +405,7 @@ $(document).ready(function () {
"should have the right facet in the query");
});
});
- asyncTest('facet selection: new value existing facet', 3, function () {
+ test('facet selection: new value existing facet', {asserts: 3}, function (instance, $s) {
var field = {
get_domain: openerp.testing.noop,
get_context: openerp.testing.noop,
@@ -451,10 +420,8 @@ $(document).ready(function () {
}
};
- var view = makeSearchView({});
- view.appendTo($('#qunit-fixture'))
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ var view = makeSearchView(instance);
+ return view.appendTo($s)
.done(function () {
view.query.add({field: field, category: 'Dummy',
values: [{label: 'previous', value: 41}]});
@@ -470,23 +437,19 @@ $(document).ready(function () {
"should have added selected value to old one");
});
});
- asyncTest('Field', 1, function () {
+ test('Field', {asserts: 1}, function (instance) {
var view = {inputs: []};
var f = new instance.web.search.Field({attrs: {}}, {}, view);
- f.complete('foo')
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ return f.complete('foo')
.done(function (completions) {
ok(_(completions).isEmpty(), "field should not provide any completion");
});
});
- asyncTest('CharField', 6, function () {
+ test('CharField', {asserts: 6}, function (instance) {
var view = {inputs: []};
var f = new instance.web.search.CharField(
{attrs: {string: "Dummy"}}, {}, view);
- f.complete('foo<')
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ return f.complete('foo<')
.done(function (completions) {
equal(completions.length, 1, "should provide a single completion");
var c = completions[0];
@@ -503,15 +466,13 @@ $(document).ready(function () {
"facet should have single value using completion item");
});
});
- asyncTest('Selection: match found', 14, function () {
+ test('Selection: match found', {asserts: 14}, function (instance) {
var view = {inputs: []};
var f = new instance.web.search.SelectionField(
{attrs: {string: "Dummy"}},
{selection: [[1, "Foo"], [2, "Bar"], [3, "Baz"], [4, "Bazador"]]},
view);
- f.complete("ba")
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ return f.complete("ba")
.done(function (completions) {
equal(completions.length, 4,
"should provide two completions and a section title");
@@ -536,20 +497,18 @@ $(document).ready(function () {
deepEqual(c3.facet.values, [{label: "Bazador", value: 4}]);
});
});
- asyncTest('Selection: no match', 1, function () {
+ test('Selection: no match', {asserts: 1}, function (instance) {
var view = {inputs: []};
var f = new instance.web.search.SelectionField(
{attrs: {string: "Dummy"}},
{selection: [[1, "Foo"], [2, "Bar"], [3, "Baz"], [4, "Bazador"]]},
view);
- f.complete("qux")
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ return f.complete("qux")
.done(function (completions) {
ok(!completions, "if no value matches the needle, no completion shall be provided");
});
});
- asyncTest('Date', 6, function () {
+ test('Date', {asserts: 6}, function (instance) {
instance.web._t.database.parameters = {
date_format: '%Y-%m-%d',
time_format: '%H:%M:%S'
@@ -557,9 +516,7 @@ $(document).ready(function () {
var view = {inputs: []};
var f = new instance.web.search.DateField(
{attrs: {string: "Dummy"}}, {type: 'datetime'}, view);
- f.complete('2012-05-21T21:21:21')
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ return f.complete('2012-05-21T21:21:21')
.done(function (completions) {
equal(completions.length, 1, "should provide a single completion");
var c = completions[0];
@@ -573,21 +530,17 @@ $(document).ready(function () {
new Date(2012, 4, 21, 21, 21, 21).getTime());
});
});
- asyncTest("M2O", 15, function () {
- instance.session.responses['/web/dataset/call_kw'] = function (req) {
- equal(req.params.method, "name_search");
- equal(req.params.model, "dummy.model");
- deepEqual(req.params.args, []);
- deepEqual(req.params.kwargs.name, 'bob');
- return {result: [[42, "choice 1"], [43, "choice @"]]}
- };
+ test("M2O", {asserts: 13}, function (instance, $s, mock) {
+ mock('dummy.model:name_search', function (args, kwargs) {
+ deepEqual(args, []);
+ strictEqual(kwargs.name, 'bob');
+ return [[42, "choice 1"], [43, "choice @"]];
+ });
var view = {inputs: []};
var f = new instance.web.search.ManyToOneField(
{attrs: {string: 'Dummy'}}, {relation: 'dummy.model'}, view);
- f.complete("bob")
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ return f.complete("bob")
.done(function (c) {
equal(c.length, 3, "should return results + title");
var title = c[0];
@@ -607,38 +560,29 @@ $(document).ready(function () {
deepEqual(f2.values.toJSON(), [{label: 'choice @', value: 43}]);
});
});
- asyncTest("M2O no match", 5, function () {
- instance.session.responses['/web/dataset/call_kw'] = function (req) {
- equal(req.params.method, "name_search");
- equal(req.params.model, "dummy.model");
- deepEqual(req.params.args, []);
- deepEqual(req.params.kwargs.name, 'bob');
- return {result: []}
- };
+ test("M2O no match", {asserts: 3}, function (instance, $s, mock) {
+ mock('dummy.model:name_search', function (args, kwargs) {
+ deepEqual(args, []);
+ strictEqual(kwargs.name, 'bob');
+ return [];
+ });
var view = {inputs: []};
var f = new instance.web.search.ManyToOneField(
{attrs: {string: 'Dummy'}}, {relation: 'dummy.model'}, view);
- f.complete("bob")
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ return f.complete("bob")
.done(function (c) {
ok(!c, "no match should yield no completion");
});
});
-
- module('search-serialization', {
- setup: function () {
- instance = openerp.testing.instanceFor('search');
-
- openerp.testing.loadTemplate(instance);
-
- openerp.testing.mockifyRPC(instance);
- }
- });
- asyncTest('No facet, no call', 6, function () {
+});
+openerp.testing.section('search-serialization', {
+ dependencies: ['web.search'],
+ rpc: 'mock',
+ templates: true
+}, function (test) {
+ test('No facet, no call', {asserts: 6}, function (instance, $s) {
var got_domain = false, got_context = false, got_groupby = false;
- var $fix = $('#qunit-fixture');
- var view = makeSearchView({
+ var view = makeSearchView(instance, {
get_domain: function () {
got_domain = true;
return null;
@@ -654,11 +598,9 @@ $(document).ready(function () {
});
var ds, cs, gs;
view.on('search_data', this, function (d, c, g) {
- ds = d, cs = c, gs = g;
+ ds = d; cs = c; gs = g;
});
- view.appendTo($fix)
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ return view.appendTo($s)
.done(function () {
view.do_search();
ok(!got_domain, "no facet, should not have fetched domain");
@@ -671,10 +613,9 @@ $(document).ready(function () {
ok(_(gs).isEmpty(), "groupby list should be empty");
})
});
- asyncTest('London, calling', 8, function () {
+ test('London, calling', {asserts: 8}, function (instance, $fix) {
var got_domain = false, got_context = false, got_groupby = false;
- var $fix = $('#qunit-fixture');
- var view = makeSearchView({
+ var view = makeSearchView(instance, {
get_domain: function (facet) {
equal(facet.get('category'), "Dummy");
deepEqual(facet.values.toJSON(), [{label: "42", value: 42}]);
@@ -692,11 +633,9 @@ $(document).ready(function () {
}, {dummy: 42});
var ds, cs, gs;
view.on('search_data', this, function (d, c, g) {
- ds = d, cs = c, gs = g;
+ ds = d; cs = c; gs = g;
});
- view.appendTo($fix)
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ return view.appendTo($fix)
.done(function () {
view.do_search();
ok(got_domain, "should have fetched domain");
@@ -709,9 +648,8 @@ $(document).ready(function () {
ok(_(gs).isEmpty(), "groupby list should be empty");
})
});
- asyncTest('Generate domains', 1, function () {
- var $fix = $('#qunit-fixture');
- var view = makeSearchView({
+ test('Generate domains', {asserts: 1}, function (instance, $fix) {
+ var view = makeSearchView(instance, {
get_domain: function (facet) {
return facet.values.map(function (value) {
return ['win', '4', value.get('value')];
@@ -720,9 +658,7 @@ $(document).ready(function () {
}, {dummy: 42});
var ds;
view.on('search_data', this, function (d) { ds = d; });
- view.appendTo($fix)
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ return view.appendTo($fix)
.done(function () {
view.do_search();
deepEqual(ds, [[['win', '4', 42]]],
@@ -730,7 +666,9 @@ $(document).ready(function () {
});
});
- test('Field single value, default domain & context', function () {
+ test('Field single value, default domain & context', {
+ rpc: false
+ }, function (instance) {
var f = new instance.web.search.Field({}, {name: 'foo'}, {inputs: []});
var facet = new instance.web.search.Facet({
field: f,
@@ -742,7 +680,9 @@ $(document).ready(function () {
equal(f.get_context(facet), null,
"default field context is null");
});
- test('Field multiple values, default domain & context', function () {
+ test('Field multiple values, default domain & context', {
+ rpc: false
+ }, function (instance) {
var f = new instance.web.search.Field({}, {name: 'foo'}, {inputs: []});
var facet = new instance.web.search.Facet({
field: f,
@@ -763,7 +703,9 @@ $(document).ready(function () {
equal(f.get_context(facet), null,
"default field context is null");
});
- test('Field single value, custom domain & context', function () {
+ test('Field single value, custom domain & context', {
+ rpc: false
+ }, function (instance) {
var f = new instance.web.search.Field({attrs:{
context: "{'bob': self}",
filter_domain: "[['edmund', 'is', self]]"
@@ -793,7 +735,9 @@ $(document).ready(function () {
self: "great"
}, "evaluation context should hold facet value as self");
});
- test("M2O default", function () {
+ test("M2O default", {
+ rpc: false
+ }, function (instance) {
var f = new instance.web.search.ManyToOneField(
{}, {name: 'foo'}, {inputs: []});
var facet = new instance.web.search.Facet({
@@ -806,7 +750,9 @@ $(document).ready(function () {
deepEqual(f.get_context(facet), {default_foo: 42},
"m2o should use value as context default");
});
- test("M2O default multiple values", function () {
+ test("M2O default multiple values", {
+ rpc: false
+ }, function (instance) {
var f = new instance.web.search.ManyToOneField(
{}, {name: 'foo'}, {inputs: []});
var facet = new instance.web.search.Facet({
@@ -823,7 +769,9 @@ $(document).ready(function () {
equal(f.get_context(facet), null,
"m2o should not have default context in case of multiple values");
});
- test("M2O custom operator", function () {
+ test("M2O custom operator", {
+ rpc: false
+ }, function (instance) {
var f = new instance.web.search.ManyToOneField(
{attrs: {operator: 'boos'}}, {name: 'foo'}, {inputs: []});
var facet = new instance.web.search.Facet({
@@ -836,7 +784,9 @@ $(document).ready(function () {
deepEqual(f.get_context(facet), {default_foo: 42},
"m2o should use value as context default");
});
- test("M2O custom domain & context", function () {
+ test("M2O custom domain & context", {
+ rpc: false
+ }, function (instance) {
var f = new instance.web.search.ManyToOneField({attrs: {
context: "{'whee': self}",
filter_domain: "[['filter', 'is', self]]"
@@ -862,7 +812,7 @@ $(document).ready(function () {
}, "custom context's self should be label");
});
- asyncTest('FilterGroup', 6, function () {
+ test('FilterGroup', {asserts: 6}, function (instance) {
var view = {inputs: [], query: {on: function () {}}};
var filter_a = new instance.web.search.Filter(
{attrs: {name: 'a', context: 'c1', domain: 'd1'}}, view);
@@ -872,9 +822,7 @@ $(document).ready(function () {
{attrs: {name: 'c', context: 'c3', domain: 'd3'}}, view);
var group = new instance.web.search.FilterGroup(
[filter_a, filter_b, filter_c], view);
- group.facet_for_defaults({a: true, c: true})
- .always(start)
- .fail(function (error) { ok(false, error && error.message); })
+ return group.facet_for_defaults({a: true, c: true})
.done(function (facet) {
var model = facet;
if (!(model instanceof instance.web.search.Facet)) {
@@ -897,19 +845,14 @@ $(document).ready(function () {
ok(!context.get_eval_context(), "context should have no evaluation context");
});
});
-
- module('removal', {
- setup: function () {
- instance = openerp.testing.instanceFor('search');
-
- openerp.testing.loadTemplate(instance);
-
- openerp.testing.mockifyRPC(instance);
- }
- });
- asyncTest('clear button', function () {
- var $fix = $('#qunit-fixture');
- var view = makeSearchView({
+});
+openerp.testing.section('removal', {
+ dependencies: ['web.search'],
+ rpc: 'mock',
+ templates: true
+}, function (test) {
+ test('clear button', {asserts: 2}, function (instance, $fix) {
+ var view = makeSearchView(instance, {
facet_for_defaults: function (defaults) {
return $.when({
field: this,
@@ -918,31 +861,22 @@ $(document).ready(function () {
});
}
}, {dummy: 42});
- view.appendTo($fix)
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ return view.appendTo($fix)
.done(function () {
equal(view.query.length, 1, "view should have default facet");
$fix.find('.oe_searchview_clear').click();
equal(view.query.length, 0, "cleared view should not have any facet");
});
});
-
- module('drawer', {
- setup: function () {
- instance = openerp.testing.instanceFor('search');
-
- openerp.testing.loadTemplate(instance);
-
- openerp.testing.mockifyRPC(instance);
- }
- });
- asyncTest('is-drawn', 2, function () {
- var view = makeSearchView();
- var $fix = $('#qunit-fixture');
- view.appendTo($fix)
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+});
+openerp.testing.section('drawer', {
+ dependencies: ['web.search'],
+ rpc: 'mock',
+ templates: true
+}, function (test) {
+ test('is-drawn', {asserts: 2}, function (instance, $fix) {
+ var view = makeSearchView(instance);
+ return view.appendTo($fix)
.done(function () {
ok($fix.find('.oe_searchview_filters').length,
"filters drawer control has been drawn");
@@ -950,50 +884,44 @@ $(document).ready(function () {
"filters advanced search has been drawn");
});
});
-
- module('filters', {
- setup: function () {
- instance = openerp.testing.instanceFor('search');
-
- openerp.testing.loadTemplate(instance);
-
- openerp.testing.mockifyRPC(instance, {
- '/web/searchview/load': function () {
- // view with a single group of filters
- return {result: {fields_view: {
- type: 'search',
- fields: {},
- arch: {
- tag: 'search',
- attrs: {},
- children: [{
- tag: 'filter',
- attrs: { string: "Foo1", domain: [ ['foo', '=', '1'] ] },
- children: []
- }, {
- tag: 'filter',
- attrs: {
- name: 'foo2',
- string: "Foo2",
- domain: [ ['foo', '=', '2'] ] },
- children: []
- }, {
- tag: 'filter',
- attrs: { string: "Foo3", domain: [ ['foo', '=', '3'] ] },
- children: []
- }]
- }
- }}};
+});
+openerp.testing.section('filters', {
+ dependencies: ['web.search'],
+ rpc: 'mock',
+ templates: true,
+ setup: function (instance, $s, mock) {
+ mock('/web/searchview/load', function () {
+ // view with a single group of filters
+ return {fields_view: {
+ type: 'search',
+ fields: {},
+ arch: {
+ tag: 'search',
+ attrs: {},
+ children: [{
+ tag: 'filter',
+ attrs: { string: "Foo1", domain: [ ['foo', '=', '1'] ] },
+ children: []
+ }, {
+ tag: 'filter',
+ attrs: {
+ name: 'foo2',
+ string: "Foo2",
+ domain: [ ['foo', '=', '2'] ] },
+ children: []
+ }, {
+ tag: 'filter',
+ attrs: { string: "Foo3", domain: [ ['foo', '=', '3'] ] },
+ children: []
+ }]
}
- });
- }
- });
- asyncTest('drawn', 3, function () {
- var view = makeSearchView();
- var $fix = $('#qunit-fixture');
- view.appendTo($fix)
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ }};
+ });
+ }
+}, function (test) {
+ test('drawn', {asserts: 3}, function (instance, $fix) {
+ var view = makeSearchView(instance);
+ return view.appendTo($fix)
.done(function () {
var $fs = $fix.find('.oe_searchview_filters ul');
// 3 filters, 1 filtergroup, 1 custom filters widget,
@@ -1006,12 +934,9 @@ $(document).ready(function () {
"Text content of first filter option should match filter string");
});
});
- asyncTest('click adding from empty query', 4, function () {
- var view = makeSearchView();
- var $fix = $('#qunit-fixture');
- view.appendTo($fix)
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ test('click adding from empty query', {asserts: 4}, function (instance, $fix) {
+ var view = makeSearchView(instance);
+ return view.appendTo($fix)
.done(function () {
var $fs = $fix.find('.oe_searchview_filters ul');
$fs.children(':eq(2)').trigger('click');
@@ -1025,12 +950,9 @@ $(document).ready(function () {
"value should be third filter");
});
});
- asyncTest('click adding from existing query', 4, function () {
- var view = makeSearchView({}, {foo2: true});
- var $fix = $('#qunit-fixture');
- view.appendTo($fix)
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ test('click adding from existing query', {asserts: 4}, function (instance, $fix) {
+ var view = makeSearchView(instance, {}, {foo2: true});
+ return view.appendTo($fix)
.done(function () {
var $fs = $fix.find('.oe_searchview_filters ul');
$fs.children(':eq(2)').trigger('click');
@@ -1045,16 +967,13 @@ $(document).ready(function () {
"second value should be clicked filter");
});
});
- asyncTest('click removing from query', 4, function () {
+ test('click removing from query', {asserts: 4}, function (instance, $fix) {
var calls = 0;
- var view = makeSearchView({}, {foo2: true});
+ var view = makeSearchView(instance, {}, {foo2: true});
view.on('search_data', null, function () {
++calls;
});
- var $fix = $('#qunit-fixture');
- view.appendTo($fix)
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ return view.appendTo($fix)
.done(function () {
var $fs = $fix.find('.oe_searchview_filters ul');
// sanity check
@@ -1065,29 +984,19 @@ $(document).ready(function () {
strictEqual(calls, 1, "one search should have been triggered");
});
});
+});
+openerp.testing.section('saved_filters', {
+ dependencies: ['web.search'],
+ rpc: 'mock',
+ templates: true
+}, function (test) {
+ test('checkboxing', {asserts: 6}, function (instance, $fix, mock) {
+ var view = makeSearchView(instance);
+ mock('/web/searchview/get_filters', function () {
+ return [{ name: "filter name", user_id: 42 }];
+ });
- module('saved_filters', {
- setup: function () {
- instance = openerp.testing.instanceFor('search');
-
- openerp.testing.loadTemplate(instance);
-
- openerp.testing.mockifyRPC(instance);
- }
- });
- asyncTest('checkboxing', 6, function () {
- var view = makeSearchView();
- instance.session.responses['/web/searchview/get_filters'] = function () {
- return {result: [{
- name: "filter name",
- user_id: 42
- }]};
- };
- var $fix = $('#qunit-fixture');
-
- view.appendTo($fix)
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ return view.appendTo($fix)
.done(function () {
var $row = $fix.find('.oe_searchview_custom li:first').click();
@@ -1104,44 +1013,31 @@ $(document).ready(function () {
"should have no value set");
})
});
- asyncTest('removal', 1, function () {
- var view = makeSearchView();
- instance.session.responses['/web/searchview/get_filters'] = function () {
- return {result: [{
- name: "filter name",
- user_id: 42
- }]};
- };
- var $fix = $('#qunit-fixture');
+ test('removal', {asserts: 1}, function (instance, $fix, mock) {
+ var view = makeSearchView(instance);
+ mock('/web/searchview/get_filters', function () {
+ return [{ name: "filter name", user_id: 42 }];
+ });
- view.appendTo($fix)
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ return view.appendTo($fix)
.done(function () {
var $row = $fix.find('.oe_searchview_custom li:first').click();
view.query.remove(view.query.at(0));
ok(!$row.hasClass('oe_selected'),
"should not be checked anymore");
- })
- });
-
- module('advanced', {
- setup: function () {
- instance = openerp.testing.instanceFor('search');
-
- openerp.testing.loadTemplate(instance);
-
- openerp.testing.mockifyRPC(instance);
- }
+ });
});
- asyncTest('single-advanced', 6, function () {
- var view = makeSearchView();
- var $fix = $('#qunit-fixture');
-
- view.appendTo($fix)
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+});
+openerp.testing.section('advanced', {
+ dependencies: ['web.search'],
+ rpc: 'mock',
+ templates: true
+}, function (test) {
+ test('single-advanced', {asserts: 6}, function (instance, $fix) {
+ var view = makeSearchView(instance);
+
+ return view.appendTo($fix)
.done(function () {
var $advanced = $fix.find('.oe_searchview_advanced');
// open advanced search (not actually useful)
@@ -1174,13 +1070,10 @@ $(document).ready(function () {
"advanced search facet should return proposed domain");
});
});
- asyncTest('multiple-advanced', 3, function () {
- var view = makeSearchView();
- var $fix = $('#qunit-fixture');
+ test('multiple-advanced', {asserts: 3}, function (instance, $fix) {
+ var view = makeSearchView(instance);
- view.appendTo($fix)
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ return view.appendTo($fix)
.done(function () {
var $advanced = $fix.find('.oe_searchview_advanced');
// open advanced search (not actually useful)
diff --git a/addons/web/static/test/test.html b/addons/web/static/test/test.html
deleted file mode 100644
index 185125659cc..00000000000
--- a/addons/web/static/test/test.html
+++ /dev/null
@@ -1,62 +0,0 @@
-<!DOCTYPE html>
-<html style="height: 100%">
-<head>
- <meta http-equiv="content-type" content="text/html; charset=utf-8" />
- <title>OpenERP Web Test Suite</title>
- <link rel="shortcut icon" href="/web/static/src/img/favicon.ico" type="image/x-icon"/>
-
- <link rel="stylesheet" href="/web/static/lib/qunit/qunit.css">
- <script src="/web/static/lib/qunit/qunit.js" type="text/javascript"></script>
-
- <script src="/web/static/lib/underscore/underscore.js" type="text/javascript"></script>
- <script src="/web/static/lib/underscore/underscore.string.js" type="text/javascript"></script>
- <script src="/web/static/lib/backbone/backbone.js" type="text/javascript"></script>
-
- <!-- jquery -->
- <script src="/web/static/lib/jquery/jquery-1.8.2.js"></script>
- <script src="/web/static/lib/jquery.ui/js/jquery-ui-1.9.1.custom.js"></script>
- <script src="/web/static/lib/jquery.ba-bbq/jquery.ba-bbq.js"></script>
-
- <script src="/web/static/lib/datejs/globalization/en-US.js"></script>
- <script src="/web/static/lib/datejs/core.js"></script>
- <script src="/web/static/lib/datejs/parser.js"></script>
- <script src="/web/static/lib/datejs/sugarpak.js"></script>
- <script src="/web/static/lib/datejs/extras.js"></script>
-
- <script src="/web/static/lib/qweb/qweb2.js"></script>
-
- <script src="/web/static/lib/py.js/lib/py.js"></script>
-
- <script src="/web/static/src/js/boot.js"></script>
- <script src="/web/static/src/js/corelib.js"></script>
- <script src="/web/static/src/js/coresetup.js"></script>
- <script src="/web/static/src/js/dates.js"></script>
- <script src="/web/static/src/js/formats.js"></script>
- <script src="/web/static/src/js/chrome.js"></script>
- <script src="/web/static/src/js/data.js"></script>
- <script src="/web/static/src/js/views.js"></script>
- <script src="/web/static/src/js/search.js"></script>
- <script src="/web/static/src/js/view_form.js"></script>
- <script src="/web/static/src/js/view_list.js"></script>
- <script src="/web/static/src/js/view_list_editable.js"></script>
-
- <script src="/web/static/test/testing.js"></script>
- <script type="text/javascript">
- QUnit.config.testTimeout = 2000;
- </script>
-</head>
- <body id="oe" class="openerp">
- <div id="qunit"></div>
- <div id="qunit-fixture"></div>
- </body>
- <script type="text/javascript" src="/web/static/test/class.js"></script>
- <script type="text/javascript" src="/web/static/test/registry.js"></script>
- <script type="text/javascript" src="/web/static/test/form.js"></script>
- <script type="text/javascript" src="/web/static/test/list-utils.js"></script>
- <script type="text/javascript" src="/web/static/test/formats.js"></script>
- <script type="text/javascript" src="/web/static/test/rpc.js"></script>
- <script type="text/javascript" src="/web/static/test/evals.js"></script>
- <script type="text/javascript" src="/web/static/test/search.js"></script>
- <script type="text/javascript" src="/web/static/test/Widget.js"></script>
- <script type="text/javascript" src="/web/static/test/list-editable.js"></script>
-</html>
diff --git a/addons/web/static/test/testing.js b/addons/web/static/test/testing.js
index 1be8e94e74b..5bd008ad7ec 100644
--- a/addons/web/static/test/testing.js
+++ b/addons/web/static/test/testing.js
@@ -1,97 +1,244 @@
-// Test support structures and methods for OpenERP
-openerp.testing = (function () {
- var xhr = QWeb2.Engine.prototype.get_xhr();
- xhr.open('GET', '/web/static/src/xml/base.xml', false);
- xhr.send(null);
- var doc = xhr.responseXML;
-
- var dependencies = {
- corelib: [],
- coresetup: ['corelib'],
- data: ['corelib', 'coresetup'],
- dates: [],
- formats: ['coresetup', 'dates'],
- chrome: ['corelib', 'coresetup'],
- views: ['corelib', 'coresetup', 'data', 'chrome'],
- search: ['data', 'coresetup', 'formats'],
- list: ['views', 'data'],
- form: ['data', 'views', 'list', 'formats'],
- list_editable: ['list', 'form', 'data'],
+openerp.testing.section('testing.stack', function (test) {
+ // I heard you like tests, so I put tests in your testing infrastructure,
+ // so you can test what you test
+ var reject = function () {
+ // utility function, rejects a success
+ var args = _.toArray(arguments);
+ return $.Deferred(function (d) {
+ d.reject.apply(d, ["unexpected success"].concat(args));
+ });
};
+ test('direct, value, success', {asserts: 1}, function () {
+ var s = openerp.testing.Stack();
+ return s.execute(function () {
+ return 42;
+ }).then(function (val) {
+ strictEqual(val, 42, "should return the handler value");
+ });
+ });
+ test('direct, deferred, success', {asserts: 1}, function () {
+ var s = openerp.testing.Stack();
+ return s.execute(function () {
+ return $.when(42);
+ }).then(function (val) {
+ strictEqual(val, 42, "should return the handler value")
+ });
+ });
+ test('direct, deferred, failure', {asserts: 1}, function () {
+ var s = openerp.testing.Stack();
+ return s.execute(function () {
+ return $.Deferred(function (d) {
+ d.reject("failed");
+ });
+ }).then(reject, function (f) {
+ strictEqual(f, "failed", "should propagate failure");
+ return $.when();
+ });
+ });
+
+ test('successful setup', {asserts: 2}, function () {
+ var setup_done = false;
+ var s = openerp.testing.Stack();
+ return s.push(function () {
+ return $.Deferred(function (d) {
+ setTimeout(function () {
+ setup_done = true;
+ d.resolve(2);
+ }, 50);
+ });
+ }).execute(function () {
+ return 42;
+ }).then(function (val) {
+ ok(setup_done, "should have executed setup");
+ strictEqual(val, 42, "should return executed function value (not setup)");
+ });
+ });
+ test('successful teardown', {asserts: 2}, function () {
+ var teardown = false;
+ var s = openerp.testing.Stack();
+ return s.push(null, function () {
+ return $.Deferred(function (d) {
+ setTimeout(function () {
+ teardown = true;
+ d.resolve(2);
+ }, 50);
+ });
+ }).execute(function () {
+ return 42;
+ }).then(function (val) {
+ ok(teardown, "should have executed teardown");
+ strictEqual(val, 42, "should return executed function value (not setup)");
+ });
+ });
+ test('successful setup and teardown', {asserts: 3}, function () {
+ var setup = false, teardown = false;
+ var s = openerp.testing.Stack();
+ return s.push(function () {
+ return $.Deferred(function (d) {
+ setTimeout(function () {
+ setup = true;
+ d.resolve(2);
+ }, 50);
+ });
+ }, function () {
+ return $.Deferred(function (d) {
+ setTimeout(function () {
+ teardown = true;
+ d.resolve(2);
+ }, 50);
+ });
+ }).execute(function () {
+ return 42;
+ }).then(function (val) {
+ ok(setup, "should have executed setup");
+ ok(teardown, "should have executed teardown");
+ strictEqual(val, 42, "should return executed function value (not setup)");
+ });
+ });
+
+ test('multiple setups', {asserts: 2}, function () {
+ var setups = 0;
+ var s = openerp.testing.Stack();
+ return s.push(function () {
+ setups++;
+ }).push(function () {
+ setups++;
+ }).push(function () {
+ setups++;
+ }).push(function () {
+ setups++;
+ }).execute(function () {
+ return 42;
+ }).then(function (val) {
+ strictEqual(setups, 4, "should have executed all setups of stack");
+ strictEqual(val, 42);
+ });
+ });
+ test('multiple teardowns', {asserts: 2}, function () {
+ var teardowns = 0;
+ var s = openerp.testing.Stack();
+ return s.push(null, function () {
+ teardowns++;
+ }).push(null, function () {
+ teardowns++;
+ }).push(null, function () {
+ teardowns++;
+ }).push(null, function () {
+ teardowns++;
+ }).execute(function () {
+ return 42;
+ }).then(function (val) {
+ strictEqual(teardowns, 4, "should have executed all teardowns of stack");
+ strictEqual(val, 42);
+ });
+ });
+ test('holes in setups', {asserts: 2}, function () {
+ var setups = [];
+ var s = openerp.testing.Stack();
+ return s.push(function () {
+ setups.push(0);
+ }).push().push().push(function () {
+ setups.push(3);
+ }).push(function () {
+ setups.push(4);
+ }).push().push(function () {
+ setups.push(6);
+ }).execute(function () {
+ return 42;
+ }).then(function (val) {
+ deepEqual(setups, [0, 3, 4, 6],
+ "should have executed setups in correct order");
+ strictEqual(val, 42);
+ });
+ });
+ test('holes in teardowns', {asserts: 2}, function () {
+ var teardowns = [];
+ var s = openerp.testing.Stack();
+ return s.push(null, function () {
+ teardowns.push(0);
+ }).push().push().push(null, function () {
+ teardowns.push(3);
+ }).push(null, function () {
+ teardowns.push(4);
+ }).push().push(null, function () {
+ teardowns.push(6);
+ }).execute(function () {
+ return 42;
+ }).then(function (val) {
+ deepEqual(teardowns, [6, 4, 3, 0],
+ "should have executed teardowns in correct order");
+ strictEqual(val, 42);
+ });
- return {
- /**
- * Function which does not do anything
- */
- noop: function () { },
- /**
- * Loads 'base.xml' template file into qweb for the provided instance
- *
- * @param instance openerp instance being initialized, to load the template file in
- */
- loadTemplate: function (instance) {
- instance.web.qweb.add_template(doc);
- },
- /**
- * Alter provided instance's ``session`` attribute to make response
- * mockable:
- *
- * * The ``responses`` parameter can be used to provide a map of (RPC)
- * paths (e.g. ``/web/view/load``) to a function returning a response
- * to the query.
- * * ``instance.session`` grows a ``responses`` attribute which is
- * a map of the same (and is in fact initialized to the ``responses``
- * parameter if one is provided)
- *
- * Note that RPC requests to un-mocked URLs will be rejected with an
- * error message: only explicitly specified urls will get a response.
- *
- * Mocked sessions will *never* perform an actual RPC connection.
- *
- * @param instance openerp instance being initialized
- * @param {Object} [responses]
- */
- mockifyRPC: function (instance, responses) {
- var session = instance.session;
- session.responses = responses || {};
- session.rpc_function = function (url, payload) {
- var fn = this.responses[url.url + ':' + payload.params.method]
- || this.responses[url.url];
+ });
- if (!fn) {
- return $.Deferred().reject({}, 'failed',
- _.str.sprintf("Url %s not found in mock responses, with arguments %s",
- url.url, JSON.stringify(payload.params))
- ).promise();
- }
- return $.when(fn(payload));
- };
- },
- /**
- * Creates an openerp web instance loading the specified module after
- * all of its dependencies.
- *
- * @param {String} module
- * @returns OpenERP Web instance
- */
- instanceFor: function (module) {
- var instance = openerp.init([]);
- this._load(instance, module);
- return instance;
- },
- _load: function (instance, module, loaded) {
- if (!loaded) { loaded = []; }
+ test('failed setup', {asserts: 5}, function () {
+ var setup, teardown, teardown2, code;
+ return openerp.testing.Stack().push(function () {
+ setup = true;
+ }, function () {
+ teardown = true;
+ }).push(function () {
+ return $.Deferred().reject("Fail!");
+ }, function () {
+ teardown2 = true;
+ }).execute(function () {
+ code = true;
+ return 42;
+ }).then(reject, function (m) {
+ ok(setup, "should have executed first setup function");
+ ok(teardown, "should have executed first teardown function");
+ ok(!teardown2, "should not have executed second teardown function");
+ strictEqual(m, "Fail!", "should return setup failure message");
+ ok(!code, "should not have executed callback");
+ return $.when();
+ });
+ });
+ test('failed teardown', {asserts: 2}, function () {
+ var teardowns = 0;
+ return openerp.testing.Stack().push(null, function () {
+ teardowns++;
+ return $.Deferred().reject('Fail 1');
+ }).push(null, function () {
+ teardowns++;
+ }).push(null, function () {
+ teardowns++;
+ return $.Deferred().reject('Fail 3');
+ }).execute(function () {
+ return 42;
+ }).then(reject, function (m) {
+ strictEqual(teardowns, 3,
+ "should have tried executing all teardowns");
+ strictEqual(m, "Fail 3", "should return first failure message");
+ return $.when();
+ });
+ });
+ test('failed call + teardown', {asserts: 2}, function () {
+ var teardowns = 0;
+ return openerp.testing.Stack().push(null, function () {
+ teardowns++;
+ }).push(null, function () {
+ teardowns++;
+ return $.Deferred().reject('Fail 2');
+ }).execute(function () {
+ return $.Deferred().reject("code");
+ }).then(reject, function (m) {
+ strictEqual(teardowns, 2,
+ "should have tried executing all teardowns");
+ strictEqual(m, "code", "should return first failure message");
+ return $.when();
+ });
+ });
- var deps = dependencies[module];
- if (!deps) { throw new Error("Unknown dependencies for " + module); }
+ test('arguments passing', {asserts: 9}, function () {
+ var asserter = function (a, b, c) {
+ strictEqual(a, 1);
+ strictEqual(b, "foo");
+ deepEqual(c, {bar: "baz", qux: 42});
+ };
- var to_load = _.difference(deps, loaded);
- while (!_.isEmpty(to_load)) {
- this._load(instance, to_load[0], loaded);
- to_load = _.difference(deps, loaded);
- }
- openerp.web[module](instance);
- loaded.push(module);
- }
- }
-})();
+ return openerp.testing.Stack()
+ .push(asserter, asserter)
+ .execute(asserter, 1, "foo", {bar: 'baz', qux: 42});
+ });
+});