Repository: OpenRefine/OpenRefine (java)

Issue:
Manage Wikibase schemas should use a list (auto numbered) to improve display
Currently, the Manage Wikibase schemas dialog shows slight grey rows for each schema to be managed.
If one row only appears, then this looks awkward because the grey row with a schema name looks like some kind of header for the dialog, instead of a row element.  It would be better to tweak the display so that rows get numbered automatically.

### Current Results
![image](https://github.com/OpenRefine/OpenRefine/assets/986438/f30f624d-64d9-4a2e-ab62-4b341beb0759)


### Expected Behavior
Rows are numbered as in this example (number just before the schema row for `test`).
If a 2nd schema was defined then I would expect numbered rows 1. and 2.

![image](https://github.com/OpenRefine/OpenRefine/assets/986438/517d5d0f-2749-44dc-8ad0-d8d135229f88)


### Screenshots
<!-- If applicable, add screenshots to help explain your problem. -->

### Versions<!--  (please complete the following information)-->
 - Operating System: <!--  e.g. iOS, Windows 10, Linux, Ubuntu 18.04 -->
 - Browser Version: <!--  e.g. Chrome 19, Firefox 61, Safari, NOTE: OpenRefine does not support IE but works OK in most cases -->
 - JRE or JDK Version: <!--  output of "java -version" e.g. JRE 1.8.0_181 -->
 - OpenRefine: <!--  e.g. OpenRefine 3.0 Beta] -->

### Datasets
<!-- If you are allowed and are OK with making your data public, it would be awesome if you can include or attach the data causing the issue or a URL pointing to where the data is.
If you are concerned about keeping your data private, you can share it selectively by email to developers who work on the issue -->

### Additional context
From Forum support topic https://forum.openrefine.org/t/where-is-export-import-schema-option-in-or-wikidata-extension-now/784


Pull request description:
Add numbering to the list of Wikibase schemas
Fixes #5893

Changes proposed in this pull request:
- JavaScript code was edited to append a number to the schema name.
- The change should be purely visual. The schema name variables are not edited.


Patch:
diff --git a/.gitignore b/.gitignore
index 08f43909a..459392486 100644
--- a/.gitignore
+++ b/.gitignore
@@ -69,6 +69,10 @@ node_modules/
 # Ignore the node_modules folder as they are dependencies to be installed by end-user
 main/webapp/node_modules
 
+# Yarn internal files
+**/.yarn
+**/.yarnrc.yml
+
 # Ignore .gitignore among extensions as it can be used to ignore custom extensions
 
 extensions/.gitignore
diff --git a/extensions/wikibase/module/scripts/dialogs/schema-list-item.html b/extensions/wikibase/module/scripts/dialogs/schema-list-item.html
index 8ce783b1e..334f625b5 100644
--- a/extensions/wikibase/module/scripts/dialogs/schema-list-item.html
+++ b/extensions/wikibase/module/scripts/dialogs/schema-list-item.html
@@ -2,7 +2,7 @@
     <div class="wikibase-template-info">
         <span class="wikibase-name" bind="templateName"></span>
     </div>
-    <div class="wikibase-template-actions">
+    <div class="wikibase-template-actions" style="float: right;">
         <a bind="renameTemplate"></a> | 
         <a bind="downloadTemplate"></a> |
         <a bind="deleteTemplate"></a>
diff --git a/extensions/wikibase/module/styles/dialogs/schema-management-dialog.css b/extensions/wikibase/module/styles/dialogs/schema-management-dialog.css
index 01f4618e0..890e3fec2 100644
--- a/extensions/wikibase/module/styles/dialogs/schema-management-dialog.css
+++ b/extensions/wikibase/module/styles/dialogs/schema-management-dialog.css
@@ -1,5 +1,6 @@
 .schema-template-list {
-  list-style: none;
+  list-style: decimal;
+  list-style-position: inside;
   max-height: 450px;
   overflow-y: auto;
 }
@@ -10,8 +11,7 @@
 
 .schema-template-list li {
   padding: .5em;
-  display: flex;
-  justify-content: space-between;
+  display: list-item;
 }
 
 .schema-template-list li:nth-child(odd) {
diff --git a/main/src/com/google/refine/expr/functions/Length.java b/main/src/com/google/refine/expr/functions/Length.java
index d7fde5db9..6ca73c740 100644
--- a/main/src/com/google/refine/expr/functions/Length.java
+++ b/main/src/com/google/refine/expr/functions/Length.java
@@ -37,6 +37,8 @@ import java.util.Collection;
 import java.util.Properties;
 
 import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
 import com.google.refine.expr.EvalError;
 import com.google.refine.expr.HasFieldsList;
 import com.google.refine.grel.ControlFunctionRegistry;
@@ -61,6 +63,8 @@ public class Length implements Function {
                     return ((HasFieldsList) v).length();
                 } else if (v instanceof ArrayNode) {
                     return ((ArrayNode) v).size();
+                } else if (v instanceof ObjectNode) {
+                    return ((ObjectNode) v).size();
                 } else {
                     String s = (v instanceof String ? (String) v : v.toString());
                     return s.length();
diff --git a/main/src/com/google/refine/model/Row.java b/main/src/com/google/refine/model/Row.java
index b1d2f295f..9eaf291a1 100644
--- a/main/src/com/google/refine/model/Row.java
+++ b/main/src/com/google/refine/model/Row.java
@@ -80,7 +80,7 @@ public class Row implements HasFields {
 
     /**
      * Copy a row and return the copy. Note that this is a shallow copy, so if the contents of cells are changed in the
-     * original, they will be be changed in the duplicate.
+     * original, they will be changed in the duplicate.
      * 
      * @return the duplicated row
      */
diff --git a/main/src/com/google/refine/model/changes/MassRowChange.java b/main/src/com/google/refine/model/changes/MassRowChange.java
index 860495485..d79ac1f0e 100644
--- a/main/src/com/google/refine/model/changes/MassRowChange.java
+++ b/main/src/com/google/refine/model/changes/MassRowChange.java
@@ -46,6 +46,8 @@ import com.google.refine.model.Project;
 import com.google.refine.model.Row;
 import com.google.refine.util.Pool;
 
+// TODO: This replaces all rows in the project with a new set of rows, but if only a small percentage of the rows
+// are being changed, it'd be much more efficient to apply a change list of new/modified rows.
 public class MassRowChange implements Change {
 
     final protected List<Row> _newRows;
diff --git a/main/src/com/google/refine/operations/cell/MultiValuedCellSplitOperation.java b/main/src/com/google/refine/operations/cell/MultiValuedCellSplitOperation.java
index da0d93cb3..f80d9bec8 100644
--- a/main/src/com/google/refine/operations/cell/MultiValuedCellSplitOperation.java
+++ b/main/src/com/google/refine/operations/cell/MultiValuedCellSplitOperation.java
@@ -1,6 +1,6 @@
 /*
 
-Copyright 2010, Google Inc.
+Copyright 2010, 2023 Google Inc. & OpenRefine contributors
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
@@ -59,6 +59,7 @@ public class MultiValuedCellSplitOperation extends AbstractOperation {
     final protected String _mode;
     final protected String _separator;
     final protected Boolean _regex;
+    private final Pattern _pattern;
 
     final protected int[] _fieldLengths;
 
@@ -94,6 +95,11 @@ public class MultiValuedCellSplitOperation extends AbstractOperation {
         _separator = separator;
         _mode = "separator";
         _regex = regex;
+        if (_regex) {
+            _pattern = Pattern.compile(_separator, Pattern.UNICODE_CHARACTER_CLASS);
+        } else {
+            _pattern = null;
+        }
 
         _fieldLengths = null;
     }
@@ -108,7 +114,14 @@ public class MultiValuedCellSplitOperation extends AbstractOperation {
         _mode = "lengths";
         _separator = null;
         _regex = null;
+        _pattern = null;
 
+        // Make sure all of our lengths are non-negative
+        for (int i = 0; i < fieldLengths.length; i++) {
+            if (fieldLengths[i] < 0) {
+                fieldLengths[i] = 0;
+            }
+        }
         _fieldLengths = fieldLengths;
     }
 
@@ -176,45 +189,39 @@ public class MultiValuedCellSplitOperation extends AbstractOperation {
 
             Object value = oldRow.getCellValue(cellIndex);
             String s = value instanceof String ? ((String) value) : value.toString();
-            String[] values = null;
+            String[] values;
             if ("lengths".equals(_mode)) {
                 if (_fieldLengths.length > 0 && _fieldLengths[0] > 0) {
                     values = new String[_fieldLengths.length];
 
-                    int lastIndex = 0;
-
+                    int from = 0;
+                    int end = s.length();
                     for (int i = 0; i < _fieldLengths.length; i++) {
-                        int thisIndex = lastIndex;
-
-                        Object o = _fieldLengths[i];
-                        if (o instanceof Number) {
-                            thisIndex = Math.min(s.length(), lastIndex + Math.max(0, ((Number) o).intValue()));
-                        }
-
-                        values[i] = s.substring(lastIndex, thisIndex);
-                        lastIndex = thisIndex;
+                        int to = Math.min(end, from + _fieldLengths[i]);
+                        values[i] = s.substring(from, to);
+                        from = to;
                     }
+                } else {
+                    values = new String[] { s };
                 }
             } else if (_regex) {
-                Pattern pattern = Pattern.compile(_separator, Pattern.UNICODE_CHARACTER_CLASS);
-                values = pattern.split(s);
+                values = _pattern.split(s);
             } else {
                 values = StringUtils.splitByWholeSeparatorPreserveAllTokens(s, _separator);
             }
 
+            // Split didn't change anything. Just copy the row
             if (values.length < 2) {
                 newRows.add(oldRow.dup());
                 continue;
             }
 
-            // First value goes into the same row
-            {
-                Row firstNewRow = oldRow.dup();
-                firstNewRow.setCell(cellIndex, new Cell(values[0], null));
-
-                newRows.add(firstNewRow);
-            }
+            // First newly split value goes into the original cell in the existing row
+            Row firstNewRow = oldRow.dup();
+            firstNewRow.setCell(cellIndex, new Cell(values[0], null));
+            newRows.add(firstNewRow);
 
+            // For remaining values, use an empty cell, if one exists in the row, otherwise allocate a new row
             int r2 = r + 1;
             for (int v = 1; v < values.length; v++) {
                 Cell newCell = new Cell(values[v], null);
@@ -222,6 +229,7 @@ public class MultiValuedCellSplitOperation extends AbstractOperation {
                 if (r2 < project.rows.size()) {
                     Row oldRow2 = project.rows.get(r2);
                     if (oldRow2.isCellBlank(cellIndex) &&
+                    // key cell not blank means we are on next record
                             oldRow2.isCellBlank(keyCellIndex)) {
 
                         Row newRow = oldRow2.dup();
@@ -234,6 +242,7 @@ public class MultiValuedCellSplitOperation extends AbstractOperation {
                     }
                 }
 
+                // We need a new (empty) row
                 Row newRow = new Row(cellIndex + 1);
                 newRow.setCell(cellIndex, newCell);
 
diff --git a/main/webapp/modules/core/scripts/views/data-table/menu-edit-cells.js b/main/webapp/modules/core/scripts/views/data-table/menu-edit-cells.js
index f2c2625ce..087b0922a 100644
--- a/main/webapp/modules/core/scripts/views/data-table/menu-edit-cells.js
+++ b/main/webapp/modules/core/scripts/views/data-table/menu-edit-cells.js
@@ -362,14 +362,14 @@ DataTableColumnHeaderUI.extendMenu(function(column, columnHeaderUI, menu) {
           return;
         }
       } else if (mode === "cases") {
-        if(elmts.reversTranistionCases[0].checked) {
+        if(elmts.reverseTransitionCases[0].checked) {
           config.separator = "(?<=\\p{Upper}|[\\p{Upper}][\\s])(?=\\p{Lower})";
         } else {
           config.separator = "(?<=\\p{Lower}|[\\p{Lower}][\\s])(?=\\p{Upper})";
         }
         config.regex = true;
       } else if (mode === "number") {
-        if(elmts.reversTranistionNumbers[0].checked) {
+        if(elmts.reverseTransitionNumbers[0].checked) {
           config.separator = "(?<=\\p{L}|[\\p{L}][\\s])(?=\\p{Digit})";
         } else {
           config.separator = "(?<=\\p{Digit}|[\\p{Digit}][\\s])(?=\\p{L})";
diff --git a/main/webapp/modules/core/scripts/views/data-table/split-multi-valued-cells-dialog.html b/main/webapp/modules/core/scripts/views/data-table/split-multi-valued-cells-dialog.html
index 72942f18a..f544d3bd5 100644
--- a/main/webapp/modules/core/scripts/views/data-table/split-multi-valued-cells-dialog.html
+++ b/main/webapp/modules/core/scripts/views/data-table/split-multi-valued-cells-dialog.html
@@ -31,7 +31,7 @@
             <tr>
               <td width="1%"><input type="radio" name="split-by-mode" value="cases" id="$split-multi-valued-cells-by-case" /></td>
               <td colspan="2"><label for="$split-multi-valued-cells-by-case" bind="or_views_byCase"></label></td>
-              <td><input type="checkbox" bind="reversTranistionCases" id="$split-cell-transition-order-case" />
+              <td><input type="checkbox" bind="reverseTransitionCases" id="$split-cell-transition-order-case" />
               <label for="$split-cell-transition-order-case" bind="or_views_revCase"></label></td>
             </tr>
             <tr>
@@ -42,7 +42,7 @@
             <tr>
               <td width="1%"><input type="radio" name="split-by-mode" value="number" id="$split-multi-valued-cells-by-number" /></td>
               <td colspan="2"><label for="$split-multi-valued-cells-by-number" bind="or_views_byNumber"></label></td>
-              <td><input type="checkbox" bind="reversTranistionNumbers" id="$split-cell-transition-order-number" />
+              <td><input type="checkbox" bind="reverseTransitionNumbers" id="$split-cell-transition-order-number" />
               <label for="$split-cell-transition-order-number" bind="or_views_revNum"></label></td>
             </tr>
             <tr>
@@ -59,4 +59,4 @@
       <button class="button" bind="cancelButton"></button>
     </div>
   </div>
-</div>
\ No newline at end of file
+</div>
diff --git a/packaging/pom.xml b/packaging/pom.xml
index a07206e70..27c94caba 100644
--- a/packaging/pom.xml
+++ b/packaging/pom.xml
@@ -574,7 +574,7 @@
                    https://stackoverflow.com/questions/53696439/appbundle-maven-plugin-fails-on-mac-osx-with-java-10 -->
               <groupId>org.codehaus.plexus</groupId>
               <artifactId>plexus-archiver</artifactId>
-              <version>4.7.1</version>
+              <version>4.8.0</version>
             </dependency>
             <dependency>
               <groupId>org.apache.velocity</groupId>
diff --git a/pom.xml b/pom.xml
index 7522c027c..4f5ef1c2d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -81,7 +81,7 @@
     <okhttp.version>4.10.0</okhttp.version>
     <jena.version>4.8.0</jena.version>
     <poi.version>5.2.3</poi.version>
-    <commons-lang3.version>3.12.0</commons-lang3.version>
+    <commons-lang3.version>3.13.0</commons-lang3.version>
     <commons-fileupload.version>1.5</commons-fileupload.version>
     <commons-collections.version>3.2.2</commons-collections.version>
     <commons-text.version>1.10.0</commons-text.version>


Test patch:
diff --git a/main/tests/cypress/package.json b/main/tests/cypress/package.json
index c9b25e828..e480e5008 100644
--- a/main/tests/cypress/package.json
+++ b/main/tests/cypress/package.json
@@ -16,13 +16,13 @@
         "lint": "prettier --check . && eslint ."
     },
     "dependencies": {
-        "cypress": "12.17.1",
+        "cypress": "12.17.2",
         "cypress-file-upload": "^5.0.8"
     },
     "devDependencies": {
-        "eslint": "^8.45.0",
+        "eslint": "^8.46.0",
         "eslint-config-google": "^0.14.0",
-        "eslint-config-prettier": "^8.8.0",
+        "eslint-config-prettier": "^8.9.0",
         "eslint-plugin-cypress": "^2.13.3",
         "prettier": "3.0.0"
     }
diff --git a/main/tests/cypress/yarn.lock b/main/tests/cypress/yarn.lock
index 066e8d958..135b33a3e 100644
--- a/main/tests/cypress/yarn.lock
+++ b/main/tests/cypress/yarn.lock
@@ -46,15 +46,15 @@
   dependencies:
     eslint-visitor-keys "^3.3.0"
 
-"@eslint-community/regexpp@^4.4.0":
-  version "4.4.0"
-  resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.4.0.tgz#3e61c564fcd6b921cb789838631c5ee44df09403"
-  integrity sha512-A9983Q0LnDGdLPjxyXQ00sbV+K+O+ko2Dr+CZigbHWtX9pNfxlaBkMR8X1CztI73zuEyEBXTVjx7CE+/VSwDiQ==
+"@eslint-community/regexpp@^4.6.1":
+  version "4.6.2"
+  resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.6.2.tgz#1816b5f6948029c5eaacb0703b850ee0cb37d8f8"
+  integrity sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==
 
-"@eslint/eslintrc@^2.1.0":
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.0.tgz#82256f164cc9e0b59669efc19d57f8092706841d"
-  integrity sha512-Lj7DECXqIVCqnqjjHMPna4vn6GJcMgul/wuS0je9OZ9gsL0zzDpKPVtcG1HaDVc+9y+qgXneTeUMbCqXJNpH1A==
+"@eslint/eslintrc@^2.1.1":
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.1.tgz#18d635e24ad35f7276e8a49d135c7d3ca6a46f93"
+  integrity sha512-9t7ZA7NGGK8ckelF0PQCfcxIUzs1Md5rrO6U/c+FIQNanea5UZC0wqKXH4vHBccmu4ZJgZ2idtPeW7+Q2npOEA==
   dependencies:
     ajv "^6.12.4"
     debug "^4.3.2"
@@ -66,10 +66,10 @@
     minimatch "^3.1.2"
     strip-json-comments "^3.1.1"
 
-"@eslint/js@8.44.0":
-  version "8.44.0"
-  resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.44.0.tgz#961a5903c74139390478bdc808bcde3fc45ab7af"
-  integrity sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==
+"@eslint/js@^8.46.0":
+  version "8.46.0"
+  resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.46.0.tgz#3f7802972e8b6fe3f88ed1aabc74ec596c456db6"
+  integrity sha512-a8TLtmPi8xzPkCbp/OGFUo5yhRkHM2Ko9kOWP4znJr0WAhWyThaw3PnwX4vOTWOAMsV2uRt32PPDcEz63esSaA==
 
 "@humanwhocodes/config-array@^0.11.10":
   version "0.11.10"
@@ -161,7 +161,7 @@ aggregate-error@^3.0.0:
     clean-stack "^2.0.0"
     indent-string "^4.0.0"
 
-ajv@^6.10.0, ajv@^6.12.4:
+ajv@^6.12.4:
   version "6.12.6"
   resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz"
   integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
@@ -436,10 +436,10 @@ cypress-file-upload@^5.0.8:
   resolved "https://registry.yarnpkg.com/cypress-file-upload/-/cypress-file-upload-5.0.8.tgz#d8824cbeaab798e44be8009769f9a6c9daa1b4a1"
   integrity sha512-+8VzNabRk3zG6x8f8BWArF/xA/W0VK4IZNx3MV0jFWrJS/qKn8eHfa5nU73P9fOQAgwHFJx7zjg4lwOnljMO8g==
 
-cypress@12.17.1:
-  version "12.17.1"
-  resolved "https://registry.yarnpkg.com/cypress/-/cypress-12.17.1.tgz#777fdcceec4ecd642fc90795f5994853b6ea03f8"
-  integrity sha512-eKfBgO6t8waEyhegL4gxD7tcI6uTCGttu+ZU7y9Hq8BlpMztd7iLeIF4AJFAnbZH1xjX+wwgg4cRKFNSvv3VWQ==
+cypress@12.17.2:
+  version "12.17.2"
+  resolved "https://registry.yarnpkg.com/cypress/-/cypress-12.17.2.tgz#040ac55de1e811f6e037d231a2869d5ab8c29c85"
+  integrity sha512-hxWAaWbqQBzzMuadSGSuQg5PDvIGOovm6xm0hIfpCVcORsCAj/gF2p0EvfnJ4f+jK2PCiDgP6D2eeE9/FK4Mjg==
   dependencies:
     "@cypress/request" "^2.88.11"
     "@cypress/xvfb" "^1.2.4"
@@ -569,10 +569,10 @@ eslint-config-google@^0.14.0:
   resolved "https://registry.yarnpkg.com/eslint-config-google/-/eslint-config-google-0.14.0.tgz"
   integrity sha512-WsbX4WbjuMvTdeVL6+J3rK1RGhCTqjsFjX7UMSMgZiyxxaNLkoJENbrGExzERFeoTpGw3F3FypTiWAP9ZXzkEw==
 
-eslint-config-prettier@^8.8.0:
-  version "8.8.0"
-  resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz#bfda738d412adc917fd7b038857110efe98c9348"
-  integrity sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==
+eslint-config-prettier@^8.9.0:
+  version "8.9.0"
+  resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.9.0.tgz#094b6254b2804b0544f7cee535f802b6d29ee10b"
+  integrity sha512-+sbni7NfVXnOpnRadUA8S28AUlsZt9GjgFvABIRL9Hkn8KqNzOp+7Lw4QWtrwn20KzU3wqu1QoOj2m+7rKRqkA==
 
 eslint-plugin-cypress@^2.13.3:
   version "2.13.3"
@@ -581,40 +581,40 @@ eslint-plugin-cypress@^2.13.3:
   dependencies:
     globals "^11.12.0"
 
-eslint-scope@^7.2.0:
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.0.tgz#f21ebdafda02352f103634b96dd47d9f81ca117b"
-  integrity sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==
+eslint-scope@^7.2.2:
+  version "7.2.2"
+  resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f"
+  integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==
   dependencies:
     esrecurse "^4.3.0"
     estraverse "^5.2.0"
 
-eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1:
-  version "3.4.1"
-  resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz#c22c48f48942d08ca824cc526211ae400478a994"
-  integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==
+eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.2:
+  version "3.4.2"
+  resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.2.tgz#8c2095440eca8c933bedcadf16fefa44dbe9ba5f"
+  integrity sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw==
 
-eslint@^8.45.0:
-  version "8.45.0"
-  resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.45.0.tgz#bab660f90d18e1364352c0a6b7c6db8edb458b78"
-  integrity sha512-pd8KSxiQpdYRfYa9Wufvdoct3ZPQQuVuU5O6scNgMuOMYuxvH0IGaYK0wUFjo4UYYQQCUndlXiMbnxopwvvTiw==
+eslint@^8.46.0:
+  version "8.46.0"
+  resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.46.0.tgz#a06a0ff6974e53e643acc42d1dcf2e7f797b3552"
+  integrity sha512-cIO74PvbW0qU8e0mIvk5IV3ToWdCq5FYG6gWPHHkx6gNdjlbAYvtfHmlCMXxjcoVaIdwy/IAt3+mDkZkfvb2Dg==
   dependencies:
     "@eslint-community/eslint-utils" "^4.2.0"
-    "@eslint-community/regexpp" "^4.4.0"
-    "@eslint/eslintrc" "^2.1.0"
-    "@eslint/js" "8.44.0"
+    "@eslint-community/regexpp" "^4.6.1"
+    "@eslint/eslintrc" "^2.1.1"
+    "@eslint/js" "^8.46.0"
     "@humanwhocodes/config-array" "^0.11.10"
     "@humanwhocodes/module-importer" "^1.0.1"
     "@nodelib/fs.walk" "^1.2.8"
-    ajv "^6.10.0"
+    ajv "^6.12.4"
     chalk "^4.0.0"
     cross-spawn "^7.0.2"
     debug "^4.3.2"
     doctrine "^3.0.0"
     escape-string-regexp "^4.0.0"
-    eslint-scope "^7.2.0"
-    eslint-visitor-keys "^3.4.1"
-    espree "^9.6.0"
+    eslint-scope "^7.2.2"
+    eslint-visitor-keys "^3.4.2"
+    espree "^9.6.1"
     esquery "^1.4.2"
     esutils "^2.0.2"
     fast-deep-equal "^3.1.3"
@@ -637,10 +637,10 @@ eslint@^8.45.0:
     strip-ansi "^6.0.1"
     text-table "^0.2.0"
 
-espree@^9.6.0:
-  version "9.6.0"
-  resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.0.tgz#80869754b1c6560f32e3b6929194a3fe07c5b82f"
-  integrity sha512-1FH/IiruXZ84tpUlm0aCUEwMl2Ho5ilqVh0VvQXw+byAz/4SAciyHLlfmL5WYqsvD38oymdUwBss0LtK8m4s/A==
+espree@^9.6.0, espree@^9.6.1:
+  version "9.6.1"
+  resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f"
+  integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==
   dependencies:
     acorn "^8.9.0"
     acorn-jsx "^5.3.2"
diff --git a/main/tests/server/src/com/google/refine/expr/functions/LengthTests.java b/main/tests/server/src/com/google/refine/expr/functions/LengthTests.java
index 3e04d14d6..14bc957bd 100644
--- a/main/tests/server/src/com/google/refine/expr/functions/LengthTests.java
+++ b/main/tests/server/src/com/google/refine/expr/functions/LengthTests.java
@@ -27,9 +27,35 @@
 
 package com.google.refine.expr.functions;
 
+import static org.testng.Assert.assertEquals;
+
+import java.util.Properties;
+
 import org.testng.annotations.Test;
 
-import com.google.refine.util.TestUtils;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import com.google.refine.RefineTest;
+import com.google.refine.grel.Function;
+import com.google.refine.util.ParsingUtilities;
+
+public class LengthTests extends RefineTest {
+
+    static Properties bindings = new Properties();
+
+    @Test
+    public void testLength() throws JsonProcessingException {
+        Function f = new Length();
+        assertEquals(f.call(bindings, new Object[] { Long.valueOf(123) }), 3);
+        assertEquals(f.call(bindings, new Object[] { Double.valueOf(123.1) }), 5);
+        assertEquals(f.call(bindings, new Object[] { "12345" }), 5);
+
+        ArrayNode array = (ArrayNode) ParsingUtilities.mapper.readTree("[1,2,3]");
+        assertEquals(f.call(bindings, new Object[] { array }), 3);
 
-public class LengthTests {
+        ObjectNode dict = (ObjectNode) ParsingUtilities.mapper.readTree("{\"a\": \"b\", \"c\": \"d\"}");
+        assertEquals(f.call(bindings, new Object[] { dict }), 2);
+    }
 }