details:   https://code.tryton.org/tryton/commit/6c39c9437f47
branch:    default
user:      Cédric Krier <[email protected]>
date:      Wed Oct 21 12:36:12 2026 +0200
description:
        Replace ~indexOf and contains by includes in JavaScript
diffstat:

 sao/src/common.js         |  164 +++++++++++++++++++++++-----------------------
 sao/src/model.js          |   60 ++++++++--------
 sao/src/pyson.js          |   19 +++--
 sao/src/rpc.js            |    2 +-
 sao/src/sao.js            |    8 +-
 sao/src/screen.js         |   40 +++++-----
 sao/src/tab.js            |    8 +-
 sao/src/view/form.js      |   16 ++-
 sao/src/view/list_form.js |    4 +-
 sao/src/view/tree.js      |    8 +-
 sao/src/window.js         |    8 +-
 sao/tests/sao.js          |    2 +-
 12 files changed, 169 insertions(+), 170 deletions(-)

diffs (1260 lines):

diff -r 1952a66929ee -r 6c39c9437f47 sao/src/common.js
--- a/sao/src/common.js Fri Oct 30 16:00:00 2026 +0100
+++ b/sao/src/common.js Wed Oct 21 12:36:12 2026 +0200
@@ -466,7 +466,7 @@
         var parts = text.split(' ');
         for (var i = 0; i < parts.length; i++) {
             var part = parts[i];
-            if (part.contains(':')) {
+            if (part.includes(':')) {
                 var subparts = part.split(':');
                 var subconverter = [
                     converter.h, converter.m, converter.s];
@@ -502,7 +502,7 @@
                 }
             }
         }
-        if (text.contains('-')) {
+        if (text.includes('-')) {
             seconds *= -1;
         }
         return Sao.TimeDelta(null, seconds);
@@ -602,8 +602,8 @@
                 this._models = models;
             });
         },
-        contains: function(model) {
-            return ~this._models.indexOf(model);
+        includes: function(model) {
+            return this._models.includes(model);
         }
     });
     Sao.common.MODELHISTORY = new Sao.common.ModelHistory();
@@ -943,7 +943,7 @@
 
         var _model_evaluator = function(allowed_models) {
             return function(value) {
-                return ~allowed_models.indexOf(value[0]) ||
+                return allowed_models.includes(value[0]) ||
                     jQuery.isEmptyObject(allowed_models);
             };
         };
@@ -1132,21 +1132,21 @@
                     if (!nextchar) {
                         this.state = null;  // end of file
                         break;
-                    } else if (this.whitespace.contains(nextchar)) {
+                    } else if (this.whitespace.includes(nextchar)) {
                         if (this.token || quoted) {
                             break;  // emit current token
                         } else {
                             continue;
                         }
-                    } else if (this.commenters.contains(nextchar)) {
+                    } else if (this.commenters.includes(nextchar)) {
                         // TODO readline
-                    } else if (this.escape.contains(nextchar)) {
+                    } else if (this.escape.includes(nextchar)) {
                         escapedstate = 'a';
                         this.state = nextchar;
-                    } else if (!~this.nowordchars.indexOf(nextchar)) {
+                    } else if (!this.nowordchars.includes(nextchar)) {
                         this.token = nextchar;
                         this.state = 'a';
-                    } else if (this.quotes.contains(nextchar)) {
+                    } else if (this.quotes.includes(nextchar)) {
                         this.state = nextchar;
                     } else if (this.whitespace_split) {
                         this.token = nextchar;
@@ -1159,25 +1159,25 @@
                             continue;
                         }
                     }
-                } else if (this.quotes.contains(this.state)) {
+                } else if (this.quotes.includes(this.state)) {
                     quoted = true;
                     if (!nextchar) {  // end of file
                         throw 'no closing quotation';
                     }
                     if (nextchar == this.state) {
                         this.state = 'a';
-                    } else if (this.escape.contains(nextchar) &&
-                        this.escapedquotes.contains(this.state)) {
+                    } else if (this.escape.includes(nextchar) &&
+                        this.escapedquotes.includes(this.state)) {
                         escapedstate = this.state;
                         this.state = nextchar;
                     } else {
                         this.token = this.token + nextchar;
                     }
-                } else if (this.escape.contains(this.state)) {
+                } else if (this.escape.includes(this.state)) {
                     if (!nextchar) {  // end of file
                         throw 'no escaped character';
                     }
-                    if (this.quotes.contains(escapedstate) &&
+                    if (this.quotes.includes(escapedstate) &&
                         (nextchar != this.state) &&
                         (nextchar != escapedstate)) {
                         this.token = this.token + this.state;
@@ -1188,22 +1188,22 @@
                     if (!nextchar) {
                         this.state = null;  // end of file
                         break;
-                    } else if (this.whitespace.contains(nextchar)) {
+                    } else if (this.whitespace.includes(nextchar)) {
                         this.state = ' ';
                         if (this.token || quoted) {
                             break;  // emit current token
                         } else {
                             continue;
                         }
-                    } else if (this.commenters.contains(nextchar)) {
+                    } else if (this.commenters.includes(nextchar)) {
                         // TODO
-                    } else if (this.quotes.contains(nextchar)) {
+                    } else if (this.quotes.includes(nextchar)) {
                         this.state = nextchar;
-                    } else if (this.escape.contains(nextchar)) {
+                    } else if (this.escape.includes(nextchar)) {
                         escapedstate = 'a';
                         this.state = nextchar;
-                    } else if ((!~this.nowordchars.indexOf(nextchar)) ||
-                            this.quotes.contains(nextchar) ||
+                    } else if ((!this.nowordchars.includes(nextchar)) ||
+                            this.quotes.includes(nextchar) ||
                             this.whitespace_split) {
                         this.token = this.token + nextchar;
                     } else {
@@ -1294,7 +1294,7 @@
                 var is_array = function(e) {
                     return e instanceof Array;
                 };
-                if ((~['AND', 'OR'].indexOf(clause[0]) ||
+                if ((['AND', 'OR'].includes(clause[0]) ||
                             (is_array(clause[0]))) &&
                         clause.slice(1).every(is_array)) {
                     return this.stringable(clause);
@@ -1306,8 +1306,8 @@
                 }
                 if (name in this.fields) {
                     var field = this.fields[name];
-                    if (~['many2one', 'one2one', 'one2many', 'many2many']
-                        .indexOf(field.type)) {
+                    if (['many2one', 'one2one', 'one2many', 'many2many']
+                        .includes(field.type)) {
                         var test = function(value) {
                             return ((typeof value == 'string') ||
                                 (value === null));
@@ -1332,7 +1332,7 @@
             if (!domain) {
                 return true;
             }
-            if (~['AND', 'OR'].indexOf(domain[0])) {
+            if (['AND', 'OR'].includes(domain[0])) {
                 domain = domain.slice(1);
             }
             return domain.every(stringable_);
@@ -1344,7 +1344,7 @@
                     return '';
                 }
                 if ((typeof clause[0] != 'string') ||
-                        ~['AND', 'OR'].indexOf(clause[0])) {
+                        ['AND', 'OR'].includes(clause[0])) {
                     return '(' + this.string(clause) + ')';
                 }
                 var name = clause[0];
@@ -1365,7 +1365,7 @@
                 if (clause.length > 3) {
                     target = clause[3];
                 }
-                if (operator.contains('ilike')) {
+                if (operator.includes('ilike')) {
                     if (this.is_full_text(value)) {
                         value = value.slice(1, -1);
                     } else if (!this.is_like(value)) {
@@ -1380,14 +1380,14 @@
                 var def_operator = this.default_operator(field);
                 if (def_operator == operator.trim()) {
                     operator = '';
-                    if (~this.OPERATORS.indexOf(value)) {
+                    if (this.OPERATORS.includes(value)) {
                         // As the value could be interpreted as an operator
                         // the default operator must be forced
                         operator = '"" ';
                     }
-                } else if ((operator.contains(def_operator) &&
-                            (operator.contains('not') ||
-                             operator.contains('!')))) {
+                } else if ((operator.includes(def_operator) &&
+                            (operator.includes('not') ||
+                             operator.includes('!')))) {
                     operator = operator.replace(def_operator, '')
                         .replace('not', '!').trim();
                 }
@@ -1407,9 +1407,9 @@
                     }
                 }
                 var formatted_value = this.format_value(field, value, target);
-                if (~this.OPERATORS.indexOf(operator) &&
-                        ~['char', 'text', 'selection']
-                        .indexOf(field.type) &&
+                if (this.OPERATORS.includes(operator) &&
+                        ['char', 'text', 'selection']
+                        .includes(field.type) &&
                         (value === '')) {
                     formatted_value = '""';
                 }
@@ -1519,7 +1519,7 @@
                     escaped = value.replace(/%%/g, '__');
                     if (escaped.startsWith('%') || escaped.endsWith('%')) {
                         value = escaped.substring(1, escaped.length - 1);
-                    } else if (~escaped.indexOf('%')) {
+                    } else if (escaped.includes('%')) {
                         value = value.replace(/%%/g, '%');
                     }
                     operator = null;
@@ -1704,7 +1704,7 @@
             var result = [];
             for (const nex of tokens.slice(1)) {
                 if ((nex == '=') && cur &&
-                    ~this.OPERATORS.indexOf(cur + nex)) {
+                    this.OPERATORS.includes(cur + nex)) {
                     result.push(cur + nex);
                     cur = null;
                 } else {
@@ -1749,7 +1749,7 @@
                     result.push(clause);
                 };
                 var i = parts.indexOf(':');
-                if (!~i) {
+                if (i < 0) {
                     parts.forEach(push_result);
                     return result;
                 }
@@ -1785,7 +1785,7 @@
                         // empty string is also the default operator
                         var operators = [''].concat(this.OPERATORS);
                         if (((i + 1) < parts.length) &&
-                                (~operators.indexOf(parts[i + 1]))) {
+                                (operators.includes(parts[i + 1]))) {
                             name = name.concat([parts[i + 1]]);
                             i += 1;
                         } else {
@@ -1961,11 +1961,11 @@
                             operator = '=';
                         }
                     }
-                    if (~[
+                    if ([
                         'integer', 'float', 'numeric',
                         'datetime', 'timestamp', 'date',
-                        'time'].indexOf(field.type)) {
-                        if ((typeof value == 'string') && 
value.contains('..')) {
+                        'time'].includes(field.type)) {
+                        if ((typeof value == 'string') && 
value.includes('..')) {
                             var values = value.split('..', 2);
                             var lvalue = this.convert_value(field, values[0], 
this.context);
                             var rvalue = this.convert_value(field, values[1], 
this.context);
@@ -2012,7 +2012,7 @@
                     } else {
                         value = this.convert_value(field, value, this.context);
                     }
-                    if (operator.contains('like')) {
+                    if (operator.includes('like')) {
                         value = this.likify(value);
                     }
                     if (target) {
@@ -2034,7 +2034,7 @@
             var escaped = value
                 .replace(escape + '%', '')
                 .replace(escape + '_', '');
-            if (escaped.contains('%') || escaped.contains('_')) {
+            if (escaped.includes('%') || escaped.includes('_')) {
                 return value;
             } else {
                 return '%' + value + '%';
@@ -2050,7 +2050,7 @@
             escaped = escaped
                 .replace(escape + '%', '')
                 .replace(escape + '_', '');
-            if (escaped.contains('%') || escaped.contains('_')) {
+            if (escaped.includes('%') || escaped.includes('_')) {
                 return false;
             }
             return value.startsWith('%') && value.endsWith('%');
@@ -2060,7 +2060,7 @@
             var escaped = value
                 .replace(escape + '%', '')
                 .replace(escape + '_', '');
-            return escaped.contains('%') || escaped.contains('_');
+            return escaped.includes('%') || escaped.includes('_');
         },
         unescape: function(value, escape) {
             escape = escape || '\\';
@@ -2075,24 +2075,24 @@
             if (empty && value === '') {
                 return '""';
             }
-            if (value.contains('\\')) {
+            if (value.includes('\\')) {
                 value = value.replace(new RegExp('\\\\', 'g'), '\\\\');
             }
-            if (value.contains('"')) {
+            if (value.includes('"')) {
                 value = value.replace(new RegExp('"', 'g'), '\\"');
             }
             var tests = [':', ' ', '(', ')'].concat(this.OPERATORS);
             for (var i = 0; i < tests.length; i++) {
                 var test = tests[i];
-                if (value.contains(test)) {
+                if (value.includes(test)) {
                     return '"' + value + '"';
                 }
             }
             return value;
         },
         default_operator: function(field) {
-            if (~['char', 'text', 'many2one', 'many2many', 'one2many',
-                    'reference', 'one2one'].indexOf(field.type)) {
+            if (['char', 'text', 'many2one', 'many2many', 'one2many',
+                'reference', 'one2one'].includes(field.type)) {
                 return 'ilike';
             } else if (field.type == 'multiselection') {
                 return 'in';
@@ -2273,12 +2273,12 @@
                 }
                 var digit = 0;
                 var string = String(value);
-                if (string.contains('e')) {
+                if (string.includes('e')) {
                     var exp = string.split('e')[1];
                     string = string.split('e')[0];
                     digit -= parseInt(exp);
                 }
-                if (string.contains('.')) {
+                if (string.includes('.')) {
                     digit += string.replace(/0+$/, '').split('.')[1].length;
                 }
                 var factor = Number(field.factor || 1);
@@ -2458,7 +2458,7 @@
             if (expression === field_name) {
                 return locale_name;
             }
-            if (expression.contains('.')) {
+            if (expression.includes('.')) {
                 return expression.split('.').slice(1).join('.');
             }
             return expression;
@@ -2485,21 +2485,21 @@
             var field = part[0];
             var operand = part[1];
             var value = part[2];
-            if (field.contains('.')) {
+            if (field.includes('.')) {
                 // In the case where the leaf concerns a m2o then having a
                 // value in the evaluation context is deemed suffisant
                 return Boolean(context[field.split('.')[0]]);
             }
             var context_field = context[field];
-            if (!~['=', '!='].indexOf(operand) &&
+            if (!['=', '!='].includes(operand) &&
                 ((context_field === null) ||
                     (context_field === undefined) ||
                     (value === null) ||
                     (value === undefined)) &&
-                !(~['in', 'not in'].indexOf(operand) &&
+                !(['in', 'not in'].includes(operand) &&
                     ((context_field === null) ||
                         (context_field === undefined)) &&
-                    ((value instanceof Array) && ~value.indexOf(null)))) {
+                    ((value instanceof Array) && value.includes(null)))) {
                 return;
             }
             if (moment.isMoment(context_field) && !value) {
@@ -2526,7 +2526,7 @@
                     (typeof value == 'string') && context_field.length == 2) {
                 context_field = context_field.join(',');
             }
-            if (~['=', '!='].indexOf(operand) &&
+            if (['=', '!='].includes(operand) &&
                     context_field instanceof Array &&
                     typeof value == 'number') {
                 operand = {
@@ -2541,10 +2541,10 @@
             }
         },
         inverse_leaf: function(domain) {
-            if (~['AND', 'OR'].indexOf(domain)) {
+            if (['AND', 'OR'].includes(domain)) {
                 return domain;
             } else if (this.is_leaf(domain)) {
-                if (domain[1].contains('child_of') && 
!domain[0].contains('.')) {
+                if (domain[1].includes('child_of') && 
!domain[0].includes('.')) {
                     if (domain.length == 3) {
                         return domain;
                     } else {
@@ -2557,7 +2557,7 @@
             }
         },
         filter_leaf: function(domain, field, model) {
-            if (~['AND', 'OR'].indexOf(domain)) {
+            if (['AND', 'OR'].includes(domain)) {
                 return domain;
             } else if (this.is_leaf(domain)) {
                 if (domain[0].startsWith(field) && (domain.length > 3)) {
@@ -2591,10 +2591,10 @@
             }
         },
         localize_domain: function(domain, field_name, strip_target) {
-            if (~['AND', 'OR', true, false].indexOf(domain)) {
+            if (['AND', 'OR', true, false].includes(domain)) {
                 return domain;
             } else if (this.is_leaf(domain)) {
-                if (domain[1].contains('child_of')) {
+                if (domain[1].includes('child_of')) {
                     if (domain[0].split('.').length > 1) {
                         var target = domain[0].split('.').slice(1).join('.');
                         return [target].concat(domain.slice(1));
@@ -2623,7 +2623,7 @@
                 return [0, domain];
             } else if (this.is_leaf(domain)) {
                 return [1, domain];
-            } else if (~['AND', 'OR'].indexOf(domain)) {
+            } else if (['AND', 'OR'].includes(domain)) {
                 return [0, domain];
             } else {
                 var content = domain.map(this._sort_key.bind(this));
@@ -2659,7 +2659,7 @@
                 return [];
             } else if (this.is_leaf(domain)) {
                 return domain;
-            } else if (~['AND', 'OR'].indexOf(domain)) {
+            } else if (['AND', 'OR'].includes(domain)) {
                 return domain;
             } else {
                 var sorted_elements = domain.map(this.sort.bind(this));
@@ -2675,7 +2675,7 @@
             var value2reference = function(value) {
                 var model = null;
                 var ref_id = null;
-                if ((typeof(value) == 'string') && value.contains(',')) {
+                if ((typeof(value) == 'string') && value.includes(',')) {
                     var split = value.split(',');
                     var result = split.splice(0, 1);
                     result.push(split.join(','));
@@ -2701,7 +2701,7 @@
                 return [model, ref_id];
             };
 
-            if (~['AND', 'OR'].indexOf(domain)) {
+            if (['AND', 'OR'].includes(domain)) {
                 return domain;
             } else if (this.is_leaf(domain)) {
                 if (domain[0] == reference) {
@@ -2749,7 +2749,7 @@
                             }
                             for (model in model_values) {
                                 ref_ids = model_values[model];
-                                if (~ref_ids.indexOf('%')) {
+                                if (ref_ids.includes('%')) {
                                     if (domain[1] == 'in') {
                                         new_domain.push(
                                             [reference + '.id', '!=', null,
@@ -2777,7 +2777,7 @@
             }
         },
         extract_reference_models: function(domain, field_name) {
-            if (~['AND', 'OR'].indexOf(domain)) {
+            if (['AND', 'OR'].includes(domain)) {
                 return [];
             } else if (this.is_leaf(domain)) {
                 var local_part = domain[0].split('.', 1)[0];
@@ -2793,7 +2793,7 @@
                         d, field_name);
                     for (var i=0, len=new_models.length; i < len; i++) {
                         var model = new_models[i];
-                        if (!~models.indexOf(model)) {
+                        if (!models.includes(model)) {
                             models.push(model);
                         }
                     }
@@ -2843,7 +2843,7 @@
         simplify_duplicate: function(domain) {
             var dedup_branches = [];
             var bool_op = null;
-            if (~['AND', 'OR'].indexOf(domain[0])) {
+            if (['AND', 'OR'].includes(domain[0])) {
                 bool_op = domain[0];
                 domain = domain.slice(1);
             }
@@ -2904,7 +2904,7 @@
         },
         merge: function(domain, domoperator) {
             if (jQuery.isEmptyObject(domain) ||
-                    ~['AND', 'OR'].indexOf(domain)) {
+                    ['AND', 'OR'].includes(domain)) {
                 return [];
             }
             var domain_type = domain[0] == 'OR' ? 'OR' : 'AND';
@@ -2974,7 +2974,7 @@
         },
         domain_inversion: function(domain, symbol, context={}) {
             var expression = this.parse(domain);
-            if (!~expression.variables.indexOf(symbol)) {
+            if (!expression.variables.includes(symbol)) {
                 return true;
             }
             return expression.inverse(symbol, context);
@@ -3001,16 +3001,16 @@
         if (a instanceof Array) {
             if (b instanceof Array) {
                 for (var i = 0, len = a.length; i < len; i++) {
-                    if (~b.indexOf(a[i])) {
+                    if (b.includes(a[i])) {
                         return true;
                     }
                 }
                 return false;
             } else {
-                return Boolean(~a.indexOf(b));
+                return a.includes(b);
             }
         } else {
-            return Boolean(~b.indexOf(a));
+            return b.includes(a);
         }
     };
     Sao.common.DomainInversion.sql_like = function(value, pattern, ignore_case)
@@ -3068,7 +3068,7 @@
             }
         },
         base: function(expression) {
-            if (!expression.contains('.')) {
+            if (!expression.includes('.')) {
                 return expression;
             } else {
                 return expression.split('.')[0];
@@ -3082,7 +3082,7 @@
                 if (part instanceof DomainInversion.And) {
                     var part_inversion = part.inverse(symbol, context);
                     var evaluated = typeof part_inversion == 'boolean';
-                    if (!~part.variables.indexOf(symbol)) {
+                    if (!part.variables.includes(symbol)) {
                         continue;
                     }
                     if (!evaluated) {
@@ -3135,7 +3135,7 @@
                 if (part instanceof DomainInversion.And) {
                     var part_inversion = part.inverse(symbol, context);
                     var evaluated = typeof part_inversion == 'boolean';
-                    if (!~part.variables.indexOf(symbol)) {
+                    if (!part.variables.includes(symbol)) {
                         if (evaluated && part_inversion) {
                             return true;
                         }
@@ -3314,7 +3314,7 @@
             if (url !== undefined) {
                 return jQuery.when(url);
             }
-            if (~Sao.common.LOCAL_ICONS.indexOf(icon_name)) {
+            if (Sao.common.LOCAL_ICONS.includes(icon_name)) {
                 return jQuery.get('images/' + icon_name + '.svg', null, null, 
'text')
                     .then(icon => {
                         var img_url = this._convert(icon);
@@ -4120,7 +4120,7 @@
         if (!element.is(':visible')) {
             return null;
         }
-        if (~['input', 'select', 'textarea'].indexOf(
+        if (['input', 'select', 'textarea'].includes(
             element[0].tagName.toLowerCase()) &&
             !element.prop('readonly')) {
             return element;
@@ -4245,7 +4245,7 @@
         for (var i=0; i < string.length; i++) {
             var c = string.charAt(i).toLowerCase();
             // Skip sao and browser shortcuts
-            if (!~['d', 'e', 'f', 'i', 'n', 't', 'w'].indexOf(c)) {
+            if (!['d', 'e', 'f', 'i', 'n', 't', 'w'].includes(c)) {
                 return c;
             }
         }
diff -r 1952a66929ee -r 6c39c9437f47 sao/src/model.js
--- a/sao/src/model.js  Fri Oct 30 16:00:00 2026 +0100
+++ b/sao/src/model.js  Wed Oct 21 12:36:12 2026 +0200
@@ -132,14 +132,14 @@
             // Remove previously removed or deleted records
             var record_removed = [];
             for (const record of this.record_removed) {
-                if (!~ids.indexOf(record.id)) {
+                if (!ids.includes(record.id)) {
                     record_removed.push(record);
                 }
             }
             this.record_removed = record_removed;
             var record_deleted = [];
             for (const record of this.record_deleted) {
-                if (!~ids.indexOf(record.id)) {
+                if (!ids.includes(record.id)) {
                     record_deleted.push(record);
                 }
             }
@@ -175,7 +175,7 @@
             if (record.group != this) {
                 record.group = this;
             }
-            if (this.indexOf(record) < 0) {
+            if (!this.includes(record)) {
                 this.splice(position, 0, record);
             }
             for (var record_rm of this.record_removed) {
@@ -211,19 +211,19 @@
             record, remove, force_remove=false, modified=true) {
             if (record.id >= 0) {
                 if (remove) {
-                    if (~this.record_deleted.indexOf(record)) {
+                    if (this.record_deleted.includes(record)) {
                         this.record_deleted.splice(
                                 this.record_deleted.indexOf(record), 1);
                     }
-                    if (!~this.record_removed.indexOf(record)) {
+                    if (!this.record_removed.includes(record)) {
                         this.record_removed.push(record);
                     }
                 } else {
-                    if (~this.record_removed.indexOf(record)) {
+                    if (this.record_removed.includes(record)) {
                         this.record_removed.splice(
                                 this.record_removed.indexOf(record), 1);
                     }
-                    if (!~this.record_deleted.indexOf(record)) {
+                    if (!this.record_deleted.includes(record)) {
                         this.record_deleted.push(record);
                     }
                 }
@@ -291,7 +291,7 @@
                 return record.id;
             });
             return root_group.on_write_ids(record_ids).then(reload_ids => {
-                reload_ids = reload_ids.filter(e => !~record_ids.indexOf(e));
+                reload_ids = reload_ids.filter(e => !record_ids.includes(e));
                 return this.model.execute('delete', [record_ids], context)
                     .then(() => {
                         for (const record of records) {
@@ -333,7 +333,7 @@
                 ids = [ids];
             }
             return this.on_write_ids(ids).then(to_reload => {
-                to_reload = to_reload.filter(e => !~ids.indexOf(e));
+                to_reload = to_reload.filter(e => !ids.includes(e));
                 this.root_group.reload(to_reload);
             });
         };
@@ -393,7 +393,7 @@
         array.destroy = function() {
             if (this.parent) {
                 var i = this.parent.group.children.indexOf(this);
-                if (~i) {
+                if (i >= 0) {
                     this.parent.group.children.splice(i, 1);
                 }
             }
@@ -458,7 +458,7 @@
             var inversion = new Sao.common.DomainInversion();
             var head = domain[0];
             var tail = domain.slice(1);
-            if (~['AND', 'OR'].indexOf(head)) {
+            if (['AND', 'OR'].includes(head)) {
                 // pass
             } else if (inversion.is_leaf(head)) {
                 var field = head[0];
@@ -722,9 +722,9 @@
             const selection_fields = ['selection', 'multiselection'];
             for (const fname of fnames) {
                 var fdescription = this.model.fields[fname].description;
-                if (~rec_named_fields.indexOf(fdescription.type))
+                if (rec_named_fields.includes(fdescription.type))
                     fnames_to_fetch.push(fname + '.rec_name');
-                else if (~selection_fields.indexOf(fdescription.type) &&
+                else if (selection_fields.includes(fdescription.type) &&
                     ((fdescription.loading || 'lazy') == 'eager')) {
                     fnames_to_fetch.push(fname + ':string');
                 } else if (
@@ -737,7 +737,7 @@
                     }
                 }
             }
-            if (!~fnames.indexOf('rec_name')) {
+            if (!fnames.includes('rec_name')) {
                 fnames_to_fetch.push('rec_name');
             }
             fnames_to_fetch.push('_timestamp');
@@ -777,7 +777,7 @@
                     filter = filter_group;
                 }
                 var idx = group.indexOf(this);
-                if (~idx) {
+                if (idx >= 0) {
                     var length = group.length;
                     var n = 1;
                     while ((Object.keys(id2record).length < limit) &&
@@ -1064,7 +1064,7 @@
         get_on_change_value: function(skip) {
             var value = {};
             for (var key in this.model.fields) {
-                if (skip && ~skip.indexOf(key)) {
+                if (skip && skip.includes(key)) {
                     continue;
                 }
                 if ((this.id >= 0) &&
@@ -1150,7 +1150,7 @@
                     continue;
                 }
                 for (var i = 0; i < field_names.length; i++) {
-                    if (~on_change_with.indexOf(field_names[i])) {
+                    if (on_change_with.includes(field_names[i])) {
                         break;
                     }
                 }
@@ -1262,7 +1262,7 @@
             for (var fname in this.model.fields) {
                 var field = this.model.fields[fname];
                 var autocomplete = field.description.autocomplete || [];
-                if (!~autocomplete.indexOf(fieldname)) {
+                if (!autocomplete.includes(fieldname)) {
                     continue;
                 }
                 this.do_autocomplete(fname);
@@ -1333,7 +1333,7 @@
             var result = true;
             for (var fname in this.model.fields) {
                 var field = this.model.fields[fname];
-                if (field_names && !~field_names.indexOf(fname)) {
+                if (field_names && !field_names.includes(fname)) {
                     continue;
                 }
                 if (!this.get_loaded([fname])) {
@@ -1443,10 +1443,10 @@
             return group;
         },
         get deleted() {
-            return Boolean(~this.group.record_deleted.indexOf(this));
+            return this.group.record_deleted.includes(this);
         },
         get removed() {
-            return Boolean(~this.group.record_removed.indexOf(this));
+            return this.group.record_removed.includes(this);
         },
         get readonly() {
             return (this.deleted ||
@@ -1793,7 +1793,7 @@
                     }
                     var domain_readonly = original_domain[0] == 'AND';
                     let setdefault;
-                    if (leftpart.contains('.')) {
+                    if (leftpart.includes('.')) {
                         let localpart = leftpart.split('.').slice(1).join('.');
                         setdefault = localpart == 'id';
                     } else {
@@ -2285,8 +2285,8 @@
                 let field_names = new Set();
                 for (const fieldname of value_fields) {
                     if (!(fieldname in group.model.fields) &&
-                            (!~fieldname.indexOf('.')) &&
-                            (!~fieldname.indexOf(':')) &&
+                            (!fieldname.includes('.')) &&
+                            (!fieldname.includes(':')) &&
                             (!fieldname.startsWith('_'))) {
                         field_names.add(fieldname);
                     }
@@ -2330,7 +2330,7 @@
             if (mode == 'list ids') {
                 var records_to_remove = [];
                 for (const old_record of group) {
-                    if (!~value.indexOf(old_record.id)) {
+                    if (!value.includes(old_record.id)) {
                         records_to_remove.push(old_record);
                     }
                 }
@@ -2394,8 +2394,8 @@
             var to_create = [];
             var to_write = [];
             for (const record2 of group) {
-                if (~record_removed.indexOf(record2) ||
-                        ~record_deleted.indexOf(record2)) {
+                if (record_removed.includes(record2) ||
+                        record_deleted.includes(record2)) {
                     continue;
                 }
                 var values;
@@ -2491,7 +2491,7 @@
                             for (const f of Object.keys(v)) {
                                 if (!(f in fields) &&
                                     (f != 'id') &&
-                                    (!~f.indexOf('.'))) {
+                                    (!f.includes('.'))) {
                                         new_field_names[f] = true;
                                     }
                             }
@@ -2626,8 +2626,8 @@
             var record_removed = group.record_removed;
             var record_deleted = group.record_deleted;
             for (const record2 of group) {
-                if (~record_removed.indexOf(record2) ||
-                        ~record_deleted.indexOf(record2))
+                if (record_removed.includes(record2) ||
+                        record_deleted.includes(record2))
                     continue;
                 result.push(record2.id);
             }
diff -r 1952a66929ee -r 6c39c9437f47 sao/src/pyson.js
--- a/sao/src/pyson.js  Fri Oct 30 16:00:00 2026 +0100
+++ b/sao/src/pyson.js  Wed Oct 21 12:36:12 2026 +0200
@@ -39,8 +39,11 @@
         in_(obj) {
             return new Sao.PYSON.In(this, obj);
         }
+        includes(k) {
+            return new Sao.PYSON.In(k, this);
+        }
         contains(k) {
-            return new Sao.PYSON.In(k, this);
+            return this.includes(k);
         }
         toString() {
             const params = this.__string_params__();
@@ -437,7 +440,7 @@
                             'date, datetime or timedelta';
                     }
                 } else {
-                    if (!~['number', 'object'].indexOf(typeof statement)) {
+                    if (!['number', 'object'].includes(typeof statement)) {
                         throw 'statement must be an integer, float, ' +
                             'date, datetime or timedelta';
                     }
@@ -566,13 +569,13 @@
             }
             if (this._else_statement instanceof Sao.PYSON.PYSON) {
                 for (const type of this._else_statement.types()) {
-                    if (!~types.indexOf(type)) {
+                    if (!types.includes(type)) {
                         types.push(type);
                     }
                 }
             } else {
                 const type = typeof this._else_statement;
-                if (!~types.indexOf(type)) {
+                if (!types.includes(type)) {
                     types.push(type);
                 }
             }
@@ -673,7 +676,7 @@
                     throw 'key must be a string or a number';
                 }
             } else {
-                if (!~['string', 'number'].indexOf(typeof key)) {
+                if (!['string', 'number'].includes(typeof key)) {
                     throw 'key must be a string or a number';
                 }
             }
@@ -704,7 +707,7 @@
             if (params[1] instanceof Sao.PYSON.PYSON) {
                 const args = params.slice().map(Sao.PYSON.toString);
                 args.splice(1, 1);
-                return `${params[1]}.contains(` + args.join(', ') + ')';
+                return `${params[1]}.includes(` + args.join(', ') + ')';
             } else {
                 return super.toString();
             }
@@ -716,8 +719,8 @@
 
     Sao.PYSON.In.eval_ = function(value, context) {
         if (value.v) {
-            if (value.v.indexOf) {
-                return Boolean(~value.v.indexOf(value.k));
+            if (value.v.includes) {
+                return value.v.includes(value.k);
             } else {
                 return !!value.v[value.k];
             }
diff -r 1952a66929ee -r 6c39c9437f47 sao/src/rpc.js
--- a/sao/src/rpc.js    Fri Oct 30 16:00:00 2026 +0100
+++ b/sao/src/rpc.js    Wed Oct 21 12:36:12 2026 +0200
@@ -49,7 +49,7 @@
                     description = data.error[1][2];
                     Sao.common.userwarning.run(description, msg)
                         .then(function(result) {
-                            if (!~['always', 'ok'].indexOf(result)) {
+                            if (!['always', 'ok'].includes(result)) {
                                 dfd.reject();
                                 return;
                             }
diff -r 1952a66929ee -r 6c39c9437f47 sao/src/sao.js
--- a/sao/src/sao.js    Fri Oct 30 16:00:00 2026 +0100
+++ b/sao/src/sao.js    Wed Oct 21 12:36:12 2026 +0200
@@ -10,12 +10,6 @@
 (function() {
     'use strict';
 
-    if (!('contains' in String.prototype)) {
-        String.prototype.contains = function(str, startIndex) {
-            return -1 !== String.prototype.indexOf.call(this, str, startIndex);
-        };
-    }
-
     // Browser compatibility: polyfill
     if (!Set.prototype.intersection) {
         Set.prototype.intersection = function(other) {
@@ -379,7 +373,7 @@
             }
             Sao.i18n.loadJSON(data);
         }, function() {
-            if (~lang.indexOf('_')) {
+            if (lang.includes('_')) {
                 return Sao.i18n.setlang(lang.split('_').slice(0, 
-1).join('_'));
             }
         });
diff -r 1952a66929ee -r 6c39c9437f47 sao/src/screen.js
--- a/sao/src/screen.js Fri Oct 30 16:00:00 2026 +0100
+++ b/sao/src/screen.js Wed Oct 21 12:36:12 2026 +0200
@@ -470,7 +470,7 @@
                 for (var f in domain_parser.fields) {
                     const field = domain_parser.fields[f];
                     if ((field.searchable || field.searchable === undefined) &&
-                        !field.name.contains('.')) {
+                        !field.name.includes('.')) {
                         fields.push(field);
                     }
                 }
@@ -748,7 +748,7 @@
             build_entry: function(placeholder, el) {
                 var entry = Sao.ScreenContainer.Times._super.build_entry.call(
                     this, placeholder, el);
-                if (~navigator.userAgent.indexOf("Firefox")) {
+                if (navigator.userAgent.includes("Firefox")) {
                     // time input on Firefox does not have a pop-up
                     entry.find('.icon-input').sao_hide();
                 }
@@ -997,7 +997,7 @@
             if (this.current_view) {
                 this.current_view.set_value();
                 if (this.current_record &&
-                        !~this.current_record.group.indexOf(
+                        !this.current_record.group.includes(
                             this.current_record)) {
                     this.current_record = null;
                 }
@@ -1025,8 +1025,8 @@
                     result &= this.current_view.creatable == creatable;
                 }
                 if (searchable !== null) {
-                    result &= Boolean(~['tree', 'graph', 'calendar'].indexOf(
-                        this.current_view.view_type)) == searchable;
+                    result &= ['tree', 'graph', 'calendar'].includes(
+                        this.current_view.view_type) == searchable;
                 }
                 return result;
             };
@@ -1417,8 +1417,8 @@
             var deferreds = [];
             if (this.views && this.current_view) {
                 var search_prm = this.search_active(
-                        ~['tree', 'graph', 'calendar'].indexOf(
-                            this.current_view.view_type));
+                    ['tree', 'graph', 'calendar'].includes(
+                        this.current_view.view_type));
                 deferreds.push(search_prm);
                 for (const view of this.views) {
                     if (view &&
@@ -1454,7 +1454,7 @@
         _get_next_record: function() {
             var view = this.current_view;
             if (view &&
-                ~['tree', 'form', 'list-form'].indexOf(view.view_type) &&
+                ['tree', 'form', 'list-form'].includes(view.view_type) &&
                 this.current_record && this.current_record.group) {
                 var group = this.current_record.group;
                 var record = this.current_record;
@@ -1499,7 +1499,7 @@
         _get_previous_record: function() {
             var view = this.current_view;
             if (view &&
-                ~['tree', 'form', 'list-form'].indexOf(view.view_type) &&
+                ['tree', 'form', 'list-form'].includes(view.view_type) &&
                 this.current_record && this.current_record.group) {
                 var group = this.current_record.group;
                 var record = this.current_record;
@@ -1556,7 +1556,7 @@
         },
         get listed_records() {
             if (this.current_view &&
-                ~['tree', 'calendar', 'list-form'].indexOf(
+                ['tree', 'calendar', 'list-form'].includes(
                     this.current_view.view_type)) {
                 return this.current_view.listed_records;
             } else if (this.current_record) {
@@ -1675,7 +1675,7 @@
         },
         set_on_write: function(name) {
             if(name) {
-                if (!~this.group.on_write.indexOf(name)) {
+                if (!this.group.on_write.includes(name)) {
                     this.group.on_write.push(name);
                 }
             }
@@ -1750,8 +1750,8 @@
         set_cursor: function(new_, reset_view) {
             if (!this.current_view) {
                 return;
-            } else if (~['tree', 'form', 'list-form'].indexOf(
-                    this.current_view.view_type)) {
+            } else if (['tree', 'form', 'list-form']
+                .includes(this.current_view.view_type)) {
                 this.current_view.set_cursor(new_, reset_view);
             }
         },
@@ -1808,11 +1808,11 @@
                         if (record.group.parent) {
                             prms.push(record.group.parent.save(false));
                         }
-                        if (~record.group.record_deleted.indexOf(record)) {
+                        if (record.group.record_deleted.includes(record)) {
                             record.group.record_deleted.splice(
                                 record.group.record_deleted.indexOf(record), 
1);
                         }
-                        if (~record.group.record_removed.indexOf(record)) {
+                        if (record.group.record_removed.includes(record)) {
                             record.group.record_removed.splice(
                                 record.group.record_removed.indexOf(record), 
1);
                         }
@@ -2181,7 +2181,7 @@
                 return this.switch_view.apply(this, action.split(' ', 
3).slice(1));
             } else if (action == 'reload') {
                 if (this.current_view && 
-                    ~['tree', 'graph', 
'calendar'].indexOf(this.current_view.view_type) &&
+                    ['tree', 'graph', 
'calendar'].includes(this.current_view.view_type) &&
                     !this.group.parent) {
                     return 
this.search_filter(this.screen_container.get_text());
                 }
@@ -2292,7 +2292,7 @@
                             view.children_field || null] = [
                                 [], [[this.current_record.id]]];
                     }
-                } else if (~['tree', 'list-form'].indexOf(view.view_type)) {
+                } else if (['tree', 'list-form'].includes(view.view_type)) {
                     var paths;
                     if (view.view_type == 'tree') {
                         view.save_width();
@@ -2345,18 +2345,18 @@
             if (!view) {
                 return jQuery.when();
             }
-            if (!~['tree', 'form', 'list-form'].indexOf(view.view_type)) {
+            if (!['tree', 'form', 'list-form'].includes(view.view_type)) {
                 return jQuery.when();
             }
 
-            if (~this.tree_states_done.indexOf(view)) {
+            if (this.tree_states_done.includes(view)) {
                 return jQuery.when();
             }
             if (view.view_type == 'form' &&
                     !jQuery.isEmptyObject(this.tree_states_done)) {
                 return jQuery.when();
             }
-            if ((~['tree', 'list-form'].indexOf(view.view_type)) &&
+            if ((['tree', 'list-form'].includes(view.view_type)) &&
                 !parseInt(view.attributes.tree_state, 10)) {
                 this.tree_states_done.push(view);
             }
diff -r 1952a66929ee -r 6c39c9437f47 sao/src/tab.js
--- a/sao/src/tab.js    Fri Oct 30 16:00:00 2026 +0100
+++ b/sao/src/tab.js    Wed Oct 21 12:36:12 2026 +0200
@@ -70,7 +70,7 @@
                     label: Sao.i18n.gettext('View Logs...'),
                 }, {
                     id: (this.screen &&
-                        
Sao.common.MODELHISTORY.contains(this.screen.model_name)) ?
+                        
Sao.common.MODELHISTORY.includes(this.screen.model_name)) ?
                         'revision': null,
                     icon: 'tryton-history',
                     label: Sao.i18n.gettext('Show revisions...'),
@@ -407,7 +407,7 @@
         if (Sao.Tab.tabs.length) {
             var tab = Sao.Tab.tabs[0];
             return tab.close().then(function() {
-                if (!~Sao.Tab.tabs.indexOf(tab)) {
+                if (!Sao.Tab.tabs.includes(tab)) {
                     return Sao.Tab.tabs.close();
                 } else {
                     return jQuery.Deferred().reject();
@@ -587,8 +587,8 @@
                     if (screen.current_view.view_type == 'form') {
                         screen.new_();
                     }
-                    if (~['tree', 'graph', 'calendar'].indexOf(
-                            screen.current_view.view_type)) {
+                    if (['tree', 'graph', 'calendar']
+                        .includes(screen.current_view.view_type)) {
                         screen.search_filter();
                     }
                 }
diff -r 1952a66929ee -r 6c39c9437f47 sao/src/view/form.js
--- a/sao/src/view/form.js      Fri Oct 30 16:00:00 2026 +0100
+++ b/sao/src/view/form.js      Wed Oct 21 12:36:12 2026 +0200
@@ -120,7 +120,7 @@
                 widget.labelled.uniqueId();
                 widget.labelled.attr('aria-labelledby', label.el.attr('id'));
                 widget.labelled.attr('accesskey', accesskey);
-                if (~['INPUT', 'SELECT'].indexOf(
+                if (['INPUT', 'SELECT'].includes(
                     widget.labelled.get(0).tagName)) {
                     jQuery('<span/>', {
                         'data-accesskey': accesskey,
@@ -1868,7 +1868,7 @@
                     value = this._parse(this._input_format, value);
                     value = this._format(this.get_format(), value);
                     this.date.val(value).change();
-                    if (!~navigator.userAgent.indexOf("Firefox")) {
+                    if (!navigator.userAgent.includes("Firefox")) {
                         // Firefox triggers change when navigate by month/year
                         this.date.focus();
                     }
@@ -2002,7 +2002,7 @@
         _parse: Sao.common.parse_time,
         init: function(view, attributes) {
             Sao.View.Form.Time._super.init.call(this, view, attributes);
-            if (~navigator.userAgent.indexOf("Firefox")) {
+            if (navigator.userAgent.includes("Firefox")) {
                 // time input on Firefox does not have a pop-up
                 this.input.parent().sao_hide();
             }
@@ -3020,7 +3020,7 @@
                     this.read_access) {
                 event_.preventDefault();
                 this.edit();
-            } else if (~activate_keys.indexOf(event_.which) && editable) {
+            } else if (activate_keys.includes(event_.which) && editable) {
                 if (!this.attributes.completion ||
                         this.attributes.completion == "1") {
                     if (this.el.find('.dropdown').hasClass('open')) {
@@ -3032,7 +3032,7 @@
                             this.field_name)) && editable) {
                 var value = this.get_text();
                 if ((value != this.entry.val()) ||
-                        ~delete_keys.indexOf(event_.which)) {
+                        delete_keys.includes(event_.which)) {
                     this.entry.val('');
                     this.record.field_set_client(this.field_name,
                         this.value_from_id(null, ''));
@@ -4055,7 +4055,8 @@
                 if (!this.wid_completion) {
                     activate_keys.push(Sao.common.RETURN_KEYCODE);
                 }
-                if (~activate_keys.indexOf(event_.which) && 
this.wid_text.val()) {
+                if (activate_keys.includes(event_.which) &&
+                    this.wid_text.val()) {
                     this.add(event_);
                 }
             }
@@ -4468,7 +4469,8 @@
             } else if (event_.which == Sao.common.F2_KEYCODE) {
                 event_.preventDefault();
                 this.add();
-            } else if (~activate_keys.indexOf(event_.which) && 
this.entry.val()) {
+            } else if (activate_keys.includes(event_.which)
+                && this.entry.val()) {
                 this.add();
             }
         },
diff -r 1952a66929ee -r 6c39c9437f47 sao/src/view/list_form.js
--- a/sao/src/view/list_form.js Fri Oct 30 16:00:00 2026 +0100
+++ b/sao/src/view/list_form.js Wed Oct 21 12:36:12 2026 +0200
@@ -56,8 +56,8 @@
                     view_form.record = record;
                 }
 
-                if (~this.group.record_deleted.indexOf(record) ||
-                        ~this.group.record_removed.indexOf(record)) {
+                if (this.group.record_deleted.includes(record) ||
+                        this.group.record_removed.includes(record)) {
                     view_form_frame.addClass('disabled');
                 } else {
                     view_form_frame.removeClass('disabled');
diff -r 1952a66929ee -r 6c39c9437f47 sao/src/view/tree.js
--- a/sao/src/view/tree.js      Fri Oct 30 16:00:00 2026 +0100
+++ b/sao/src/view/tree.js      Wed Oct 21 12:36:12 2026 +0200
@@ -36,8 +36,8 @@
                 column.suffixes.push(
                     new Sao.View.Tree.Symbol(attributes, 1));
             }
-            if (~['url', 'email', 'callto', 'sip'
-                    ].indexOf(attributes.widget)) {
+            if (['url', 'email', 'callto', 'sip']
+                .includes(attributes.widget)) {
                 column.prefixes.push(
                     new Sao.View.Tree.Affix(attributes, attributes.widget));
             }
@@ -1296,8 +1296,8 @@
                         }else {
                             sum_ += value;
                         }
-                        if (~records_ids.indexOf(record.id) ||
-                            !selected_records){
+                        if (records_ids.includes(record.id) ||
+                            !selected_records) {
                             if (selected_sum === null){
                                 selected_sum = value;
                             }else {
diff -r 1952a66929ee -r 6c39c9437f47 sao/src/window.js
--- a/sao/src/window.js Fri Oct 30 16:00:00 2026 +0100
+++ b/sao/src/window.js Wed Oct 21 12:36:12 2026 +0200
@@ -147,7 +147,7 @@
                     var revision = this.screen.context._datetime;
                     var label;
                     if (revision &&
-                        
Sao.common.MODELHISTORY.contains(this.screen.model_name)) {
+                        
Sao.common.MODELHISTORY.includes(this.screen.model_name)) {
                         var date_format = Sao.common.date_format(
                             this.screen.context.date_format);
                         var time_format = '%H:%M:%S.%f';
@@ -577,7 +577,7 @@
             var result;
             this.screen.current_view.set_value();
             var readonly = this.screen.group.readonly;
-            if (~['RESPONSE_OK', 'RESPONSE_ACCEPT'].indexOf(response_id) &&
+            if (['RESPONSE_OK', 'RESPONSE_ACCEPT'].includes(response_id) &&
                     !readonly &&
                     this.screen.current_record) {
                 
jQuery.when(this.screen.current_record.validate()).then(validate => {
@@ -2138,7 +2138,7 @@
                     this.fields[path] = node;
 
                     // Insert relation only to real field
-                    if (item.name.indexOf('.') == -1) {
+                    if (!item.name.includes('.')) {
                         if (item.field.relation) {
                             node.children = {};
                         } else if (item.field.translate) {
@@ -2780,7 +2780,7 @@
                 for (var print_id in this.print_actions) {
                     var check = this.print_actions[print_id];
                     check.prop(
-                        'checked', ~print_ids.indexOf(parseInt(print_id, 10)));
+                        'checked', print_ids.includes(parseInt(print_id, 10)));
                 }
             });
         },
diff -r 1952a66929ee -r 6c39c9437f47 sao/tests/sao.js
--- a/sao/tests/sao.js  Fri Oct 30 16:00:00 2026 +0100
+++ b/sao/tests/sao.js  Wed Oct 21 12:36:12 2026 +0200
@@ -1232,7 +1232,7 @@
             ['id', '=', -1]));
 
         QUnit.assert.strictEqual(expr.toString(),
-            'If(Not(Eval("context", {}).contains("company")), "=", "!=")');
+            'If(Not(Eval("context", {}).includes("company")), "=", "!=")');
         });
     QUnit.test('PYSON noeval', function() {
         var decoder = new Sao.PYSON.Decoder({}, true);

Reply via email to