Repository: OpenRefine/OpenRefine (java)

Issue:
UI improvements for the "Add entity identifiers column" action
When using the "Add entity identifiers column action", the UI currently looks like this:
![image](https://github.com/OpenRefine/OpenRefine/assets/309908/a2c6aa9f-2bfa-4342-a30c-e26939fe1283)

### Proposed improvements

* the input field for the column name should be focused when the dialog opens
* the input field could be pre-filled with an automatically generated column name which does not conflict with any existing column name (for instance, if the base column is called "Name", the default column name could be "matched id for Name", perhaps?)
* the "Submit Query" label for the validation button does not make sense, as we are not submitting a query. It should be "OK" or "Create column", I would say. It should be marked as primary (default) button, which generally appears in bold I think
* the title of the dialog does not clearly separate the user-provided data (column name) from the rest. Instead of being `Add column containing entity identifiers on Name`, it could be `Add column of entity identifiers from column "Name"` (note the double quotes)

### Additional context

Discovered while reviewing #5960.
Maybe @OpenRefine/designers have more thoughts about this?

Pull request description:
Issue 5998 entity identifiers dialog improvements
Fixes #5998

Changes proposed in this pull request:
- Add quotes (") around column name in title.
- Focus on text input when dialog opens.
- Change "Submit Query" to "OK."

After:

![Add entities column dialog](https://github.com/OpenRefine/OpenRefine/assets/42903164/41d74005-7862-4807-8460-b5a83bfcc048)


Patch:
diff --git a/extensions/gdata/pom.xml b/extensions/gdata/pom.xml
index 6dc58d714..68c0165b7 100644
--- a/extensions/gdata/pom.xml
+++ b/extensions/gdata/pom.xml
@@ -85,7 +85,7 @@
     <dependency>
       <groupId>com.google.apis</groupId>
       <artifactId>google-api-services-drive</artifactId>
-      <version>v3-rev20230714-2.0.0</version>
+      <version>v3-rev20230815-2.0.0</version>
     </dependency>
     <dependency>
       <groupId>com.google.apis</groupId>
diff --git a/main/src/com/google/refine/importers/TextFormatGuesser.java b/main/src/com/google/refine/importers/TextFormatGuesser.java
index ca9c6f96b..b9a08a674 100644
--- a/main/src/com/google/refine/importers/TextFormatGuesser.java
+++ b/main/src/com/google/refine/importers/TextFormatGuesser.java
@@ -34,13 +34,13 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.UnsupportedEncodingException;
-import java.util.Arrays;
 
 import org.apache.commons.io.input.BoundedInputStream;
 import org.apache.commons.lang.StringUtils;
 
 import com.google.common.base.CharMatcher;
 import com.google.refine.importing.FormatGuesser;
+import com.google.refine.importing.ImportingUtilities;
 
 public class TextFormatGuesser implements FormatGuesser {
 
@@ -51,7 +51,7 @@ public class TextFormatGuesser implements FormatGuesser {
     @Override
     public String guess(File file, String encoding, String seedFormat) {
         try (InputStream fis = new FileInputStream(file)) {
-            if (isCompressed(file)) {
+            if (ImportingUtilities.isCompressed(file)) {
                 return "binary";
             }
             ;
@@ -132,19 +132,4 @@ public class TextFormatGuesser implements FormatGuesser {
         return null;
     }
 
-    private boolean isCompressed(File file) throws IOException {
-        // Check for common compressed file types to protect ourselves from binary data
-        try (InputStream is = new FileInputStream(file)) {
-            byte[] magic = new byte[4];
-            int count = is.read(magic);
-            if (count == 4 && Arrays.equals(magic, new byte[] { 0x50, 0x4B, 0x03, 0x04 }) || // zip
-                    Arrays.equals(magic, new byte[] { 0x50, 0x4B, 0x07, 0x08 }) ||
-                    (magic[0] == 0x1F && magic[1] == (byte) 0x8B) // gzip
-            ) {
-                return true;
-            }
-        }
-        return false;
-    }
-
 }
diff --git a/main/src/com/google/refine/importing/EncodingGuesser.java b/main/src/com/google/refine/importing/EncodingGuesser.java
index 605f93c47..797c355f1 100644
--- a/main/src/com/google/refine/importing/EncodingGuesser.java
+++ b/main/src/com/google/refine/importing/EncodingGuesser.java
@@ -1,31 +1,32 @@
 
 package com.google.refine.importing;
 
-import java.io.BufferedInputStream;
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileNotFoundException;
 import java.io.IOException;
-import java.io.InputStream;
 import java.util.List;
 
-import org.apache.commons.lang.StringUtils;
-import org.mozilla.universalchardet.UniversalDetector;
 import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.apache.commons.lang.StringUtils;
+import org.mozilla.universalchardet.UnicodeBOMInputStream;
+import org.mozilla.universalchardet.UniversalDetector;
 
 import com.google.refine.util.JSONUtilities;
 
 /**
- * This class tries to find the correct encoding based on the http://site.icu-project.org/ and the icu4j library
- * http://site.icu-project.org/home/why-use-icu4j.
+ * This class tries to find the correct encoding based on https://github.com/albfernandez/juniversalchardet which is a
+ * Java port of Mozilla's universalchardet library
+ * https://hg.mozilla.org/mozilla-central/file/tip/extensions/universalchardet/
  * 
  * @author <a href="mailto:kontakt@stundzig.de">Steffen Stundzig</a>
  */
 public final class EncodingGuesser {
 
-    public final static void guess(final ImportingJob job)
-            throws FileNotFoundException, IOException {
+    public static final String UTF_8_BOM = "UTF-8-BOM";
+
+    public static void guess(final ImportingJob job)
+            throws IOException {
         ObjectNode retrievalRecord = job.getRetrievalRecord();
         if (retrievalRecord != null) {
             ArrayNode fileRecords = JSONUtilities.getArray(retrievalRecord, "files");
@@ -38,9 +39,13 @@ public final class EncodingGuesser {
                     if (StringUtils.isBlank(encoding)) {
                         String location = JSONUtilities.getString(record, "location", null);
                         if (location != null) {
-                            try (InputStream is = new BufferedInputStream(
+                            try (UnicodeBOMInputStream is = new UnicodeBOMInputStream(
                                     new FileInputStream(new File(job.getRawDataDir(), location)))) {
                                 String detected = UniversalDetector.detectCharset(is);
+                                UnicodeBOMInputStream.BOM bom = is.getBOM();
+                                if (UnicodeBOMInputStream.BOM.UTF_8.equals(bom)) {
+                                    detected = UTF_8_BOM;
+                                }
                                 if (detected != null) {
                                     JSONUtilities.safePut(record, "encoding", detected);
                                 }
diff --git a/main/src/com/google/refine/importing/ImportingUtilities.java b/main/src/com/google/refine/importing/ImportingUtilities.java
index 27ff6a5d8..5a2f2d923 100644
--- a/main/src/com/google/refine/importing/ImportingUtilities.java
+++ b/main/src/com/google/refine/importing/ImportingUtilities.java
@@ -33,6 +33,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 package com.google.refine.importing;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
@@ -69,7 +71,6 @@ import javax.servlet.http.HttpServletResponse;
 import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
 import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
 import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
-import org.apache.commons.compress.compressors.lzma.LZMACompressorInputStream;
 import org.apache.commons.fileupload.FileItem;
 import org.apache.commons.fileupload.FileUploadException;
 import org.apache.commons.fileupload.ProgressListener;
@@ -84,6 +85,7 @@ import org.apache.hc.core5.http.ContentType;
 import org.apache.hc.core5.http.HttpEntity;
 import org.apache.hc.core5.http.HttpStatus;
 import org.apache.hc.core5.http.io.HttpClientResponseHandler;
+import org.mozilla.universalchardet.UnicodeBOMInputStream;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -349,7 +351,6 @@ public class ImportingUtilities {
                         if (httpClient.getResponse(urlString, null, responseHandler) != null) {
                             archiveCount++;
                         }
-                        ;
                         downloadCount++;
                     } else {
                         // Fallback handling for non HTTP connections (only FTP?)
@@ -435,19 +436,19 @@ public class ImportingUtilities {
 
         update.totalExpectedSize += length;
 
-        progress.setProgress("Downloading " + url.toString(),
+        progress.setProgress("Downloading " + url, // TODO: Localize
                 calculateProgressPercent(update.totalExpectedSize, update.totalRetrievedSize));
 
         long actualLength = saveStreamToFile(stream, file, update);
         JSONUtilities.safePut(fileRecord, "size", actualLength);
         if (actualLength == 0) {
-            throw new IOException("No content found in " + url.toString());
+            throw new IOException("No content found in " + url);
         } else if (length >= 0) {
             update.totalExpectedSize += (actualLength - length);
         } else {
             update.totalExpectedSize += actualLength;
         }
-        progress.setProgress("Saving " + url.toString() + " locally",
+        progress.setProgress("Saving " + url + " locally", // TODO: Localize
                 calculateProgressPercent(update.totalExpectedSize, update.totalRetrievedSize));
         return postProcessRetrievedFile(rawDataDir, file, fileRecord, fileRecords, progress);
     }
@@ -528,11 +529,23 @@ public class ImportingUtilities {
             encoding = commonEncoding;
         }
         if (encoding != null) {
-            try {
-                return new InputStreamReader(inputStream, encoding);
-            } catch (UnsupportedEncodingException e) {
-                // Ignore and fall through
+
+            // Special case for UTF-8 with BOM
+            if (EncodingGuesser.UTF_8_BOM.equals(encoding)) {
+                try {
+                    return new InputStreamReader(new UnicodeBOMInputStream(inputStream, true), UTF_8);
+                } catch (IOException e) {
+                    throw new RuntimeException("Exception from UnicodeBOMInputStream", e);
+                }
+            } else {
+                try {
+                    return new InputStreamReader(inputStream, encoding);
+                } catch (UnsupportedEncodingException e) {
+                    // This should never happen since they picked from a list of supported encodings
+                    throw new RuntimeException("Unsupported encoding: " + encoding, e);
+                }
             }
+
         }
         return new InputStreamReader(inputStream);
     }
@@ -677,7 +690,7 @@ public class ImportingUtilities {
                     || "application/x-zip-compressed".equals(contentType)
                     || "application/zip".equals(contentType)
                     || "application/x-compressed".equals(contentType)
-                    || "multipar/x-zip".equals(contentType)) {
+                    || "multipart/x-zip".equals(contentType)) {
                 return new ZipInputStream(new FileInputStream(file));
             } else if (fileName.endsWith(".kmz")) {
                 return new ZipInputStream(new FileInputStream(file));
@@ -689,13 +702,28 @@ public class ImportingUtilities {
 
     private static boolean isFileGZipped(File file) {
         int magic = 0;
-        try (RandomAccessFile raf = new RandomAccessFile(file, "r");) {
+        try (RandomAccessFile raf = new RandomAccessFile(file, "r")) {
             magic = raf.read() & 0xff | ((raf.read() << 8) & 0xff00);
         } catch (IOException ignored) {
         }
         return magic == GZIPInputStream.GZIP_MAGIC;
     }
 
+    public static boolean isCompressed(File file) throws IOException {
+        // Check for common compressed file types to protect ourselves from binary data
+        try (InputStream is = new FileInputStream(file)) {
+            byte[] magic = new byte[4];
+            int count = is.read(magic);
+            if (count == 4 && Arrays.equals(magic, new byte[] { 0x50, 0x4B, 0x03, 0x04 }) || // zip
+                    Arrays.equals(magic, new byte[] { 0x50, 0x4B, 0x07, 0x08 }) ||
+                    (magic[0] == 0x1F && magic[1] == (byte) 0x8B) // gzip
+            ) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     // FIXME: This is wasteful of space and time. We should try to process on the fly
     static private boolean explodeArchive(
             File rawDataDir,
@@ -888,7 +916,7 @@ public class ImportingUtilities {
                 }
             }
 
-            // If nothing matches the best format but we have some files,
+            // If nothing matches the best format, but we have some files,
             // then select them all
             if (fileSelectionIndexes.size() == 0 && count > 0) {
                 for (int i = 0; i < count; i++) {
diff --git a/main/webapp/modules/core/langs/translation-en.json b/main/webapp/modules/core/langs/translation-en.json
index 3faec69f3..4b0df045f 100644
--- a/main/webapp/modules/core/langs/translation-en.json
+++ b/main/webapp/modules/core/langs/translation-en.json
@@ -577,7 +577,7 @@
     "core-views/word-facet": "Word facet",
     "core-views/add-id-column": "Add entity identifiers column…",
     "core-views/add-id-column2": "Add a column containing entity identifiers from items",
-    "core-views/add-id-col": "Add column containing entity identifiers on $1",
+    "core-views/add-id-col": "Add column containing entity identifiers on \"$1\"",
     "core-views/duplicates-facet": "Duplicates facet",
     "core-views/numeric-log-facet": "Numeric log facet",
     "core-views/bounded-log-facet": "1-bounded numeric log facet",
diff --git a/main/webapp/modules/core/scripts/views/data-table/add-q-column-dialog.html b/main/webapp/modules/core/scripts/views/data-table/add-q-column-dialog.html
index 2ba2398c2..2b9dc48ca 100644
--- a/main/webapp/modules/core/scripts/views/data-table/add-q-column-dialog.html
+++ b/main/webapp/modules/core/scripts/views/data-table/add-q-column-dialog.html
@@ -5,13 +5,13 @@
     <div class="dialog-body" bind="dialogBody">
       <div class="grid-layout layout-normal layout-full"><table cols="2">
         <tr>
-          <td width="1%" style="white-space: pre;"><label for="columnNameInputId" bind="or_views_newCol"></label></td>
-          <td><input bind="columnNameInput" id="columnNameInputId" size="40" /></td>
+          <td width="1%" style="white-space: pre;"><label for="columnNameInputId" bind="or_views_newCol" ></label></td>
+          <td><input bind="columnNameInput" id="columnNameInputId" size="40" autofocus/></td>
         </tr>
       </table></div>
     </div>
     <div class="dialog-footer" bind="dialogFooter">
-      <input type="submit" class="button" bind="okButton"/>
+      <button type="submit" class="button" bind="okButton"></button>
       <button class="button" bind="cancelButton"></button>
     </div>
   </div>
diff --git a/main/webapp/modules/core/scripts/views/data-table/menu-reconcile.js b/main/webapp/modules/core/scripts/views/data-table/menu-reconcile.js
index 2ebbdd8a6..cb89df347 100644
--- a/main/webapp/modules/core/scripts/views/data-table/menu-reconcile.js
+++ b/main/webapp/modules/core/scripts/views/data-table/menu-reconcile.js
@@ -250,7 +250,9 @@ DataTableColumnHeaderUI.extendMenu(function(column, columnHeaderUI, menu) {
     elmts.cancelButton.text($.i18n('core-buttons/cancel'));
 
     var level = DialogSystem.showDialog(frame);
-    var dismiss = function() { DialogSystem.dismissUntil(level - 1); };
+
+    elmts.columnNameInput.focus();
+      var dismiss = function() { DialogSystem.dismissUntil(level - 1); };
 
     var o = DataTableView.sampleVisibleRows(column);
     
diff --git a/main/webapp/package-lock.json b/main/webapp/package-lock.json
index 824f48858..75ca6ca17 100644
--- a/main/webapp/package-lock.json
+++ b/main/webapp/package-lock.json
@@ -9,7 +9,7 @@
       "dependencies": {
         "@wikimedia/jquery.i18n": "1.0.9",
         "datejs-coolite": "1.0.0",
-        "jquery": "3.7.0",
+        "jquery": "3.7.1",
         "jquery-migrate": "3.4.1",
         "js-cookie": "3.0.5",
         "moment": "2.29.4",
@@ -35,9 +35,9 @@
       "integrity": "sha512-kppYUwn9R2pzsoChLsDlWLtKTwjf5k9Z3fx8CNKjwvhYfpadKIFoL+16dDJvftiXKwT+ixh2PRslZsfzp+1ZVA=="
     },
     "node_modules/jquery": {
-      "version": "3.7.0",
-      "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.0.tgz",
-      "integrity": "sha512-umpJ0/k8X0MvD1ds0P9SfowREz2LenHsQaxSohMZ5OMNEU2r0tf8pdeEFTHMFxWVxKNyU9rTtK3CWzUCTKJUeQ=="
+      "version": "3.7.1",
+      "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
+      "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg=="
     },
     "node_modules/jquery-migrate": {
       "version": "3.4.1",
@@ -94,9 +94,9 @@
       "integrity": "sha512-kppYUwn9R2pzsoChLsDlWLtKTwjf5k9Z3fx8CNKjwvhYfpadKIFoL+16dDJvftiXKwT+ixh2PRslZsfzp+1ZVA=="
     },
     "jquery": {
-      "version": "3.7.0",
-      "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.0.tgz",
-      "integrity": "sha512-umpJ0/k8X0MvD1ds0P9SfowREz2LenHsQaxSohMZ5OMNEU2r0tf8pdeEFTHMFxWVxKNyU9rTtK3CWzUCTKJUeQ=="
+      "version": "3.7.1",
+      "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
+      "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg=="
     },
     "jquery-migrate": {
       "version": "3.4.1",
diff --git a/main/webapp/package.json b/main/webapp/package.json
index 52f8ccdc4..44211bc1b 100644
--- a/main/webapp/package.json
+++ b/main/webapp/package.json
@@ -12,7 +12,7 @@
   "dependencies": {
     "@wikimedia/jquery.i18n": "1.0.9",
     "datejs-coolite": "1.0.0",
-    "jquery": "3.7.0",
+    "jquery": "3.7.1",
     "jquery-migrate": "3.4.1",
     "js-cookie": "3.0.5",
     "moment": "2.29.4",
diff --git a/pom.xml b/pom.xml
index 4f5ef1c2d..8b60e5d7d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -108,7 +108,7 @@
     <httpmime.version>4.5.14</httpmime.version>
     <owasp-encoder.version>1.2.3</owasp-encoder.version>
     <jasypt.version>1.9.3</jasypt.version>
-    <mockito.version>5.4.0</mockito.version>
+    <mockito.version>5.5.0</mockito.version>
 
     <!-- plugin versions -->
     <build-helper-maven-plugin.version>3.3.0</build-helper-maven-plugin.version>


Test patch:
diff --git a/main/tests/cypress/cypress/e2e/project/grid/column/reconcile/add_entity_identifiers.cy.js b/main/tests/cypress/cypress/e2e/project/grid/column/reconcile/add_entity_identifiers.cy.js
index 3686d4876..7f621a966 100644
--- a/main/tests/cypress/cypress/e2e/project/grid/column/reconcile/add_entity_identifiers.cy.js
+++ b/main/tests/cypress/cypress/e2e/project/grid/column/reconcile/add_entity_identifiers.cy.js
@@ -20,10 +20,10 @@ describe('Add entity identifiers', () => {
     // check the dialog, enter a new column name "id_column"
     cy.get('.dialog-container .dialog-header').should(
       'to.contain',
-      'Add column containing entity identifiers on species'
+      'Add column containing entity identifiers on "species"'
     );
     cy.get('.dialog-container .dialog-body input').type('id_column');
-    cy.get('.dialog-container .dialog-footer input').contains('OK').click();
+    cy.get('.dialog-container .dialog-footer button').contains('OK').click();
 
     // Check the cells content for the new column
     cy.assertCellEquals(0, 'id_column', '2253634'); // untouched
diff --git a/main/tests/cypress/package.json b/main/tests/cypress/package.json
index bb2544479..9e190c38d 100644
--- a/main/tests/cypress/package.json
+++ b/main/tests/cypress/package.json
@@ -20,10 +20,10 @@
         "cypress-file-upload": "^5.0.8"
     },
     "devDependencies": {
-        "eslint": "^8.47.0",
+        "eslint": "^8.48.0",
         "eslint-config-google": "^0.14.0",
         "eslint-config-prettier": "^9.0.0",
         "eslint-plugin-cypress": "^2.14.0",
-        "prettier": "3.0.2"
+        "prettier": "3.0.3"
     }
 }
diff --git a/main/tests/cypress/yarn.lock b/main/tests/cypress/yarn.lock
index 74b9f5efc..116e2bbbf 100644
--- a/main/tests/cypress/yarn.lock
+++ b/main/tests/cypress/yarn.lock
@@ -66,10 +66,10 @@
     minimatch "^3.1.2"
     strip-json-comments "^3.1.1"
 
-"@eslint/js@^8.47.0":
-  version "8.47.0"
-  resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.47.0.tgz#5478fdf443ff8158f9de171c704ae45308696c7d"
-  integrity sha512-P6omY1zv5MItm93kLM8s2vr1HICJH8v0dvddDhysbIuZ+vcjOHg5Zbkf1mTkcmi2JA9oBG2anOkRnW8WJTS8Og==
+"@eslint/js@8.48.0":
+  version "8.48.0"
+  resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.48.0.tgz#642633964e217905436033a2bd08bf322849b7fb"
+  integrity sha512-ZSjtmelB7IJfWD2Fvb7+Z+ChTIKWq6kjda95fLcQKNS5aheVHn4IkfgRQE3sIIzTcSLwLcLZUD9UBt+V7+h+Pw==
 
 "@humanwhocodes/config-array@^0.11.10":
   version "0.11.10"
@@ -590,15 +590,15 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4
   resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800"
   integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
 
-eslint@^8.47.0:
-  version "8.47.0"
-  resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.47.0.tgz#c95f9b935463fb4fad7005e626c7621052e90806"
-  integrity sha512-spUQWrdPt+pRVP1TTJLmfRNJJHHZryFmptzcafwSvHsceV81djHOdnEeDmkdotZyLNjDhrOasNK8nikkoG1O8Q==
+eslint@^8.48.0:
+  version "8.48.0"
+  resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.48.0.tgz#bf9998ba520063907ba7bfe4c480dc8be03c2155"
+  integrity sha512-sb6DLeIuRXxeM1YljSe1KEx9/YYeZFQWcV8Rq9HfigmdDEugjLEVEa1ozDjL6YDjBpQHPJxJzze+alxi4T3OLg==
   dependencies:
     "@eslint-community/eslint-utils" "^4.2.0"
     "@eslint-community/regexpp" "^4.6.1"
     "@eslint/eslintrc" "^2.1.2"
-    "@eslint/js" "^8.47.0"
+    "@eslint/js" "8.48.0"
     "@humanwhocodes/config-array" "^0.11.10"
     "@humanwhocodes/module-importer" "^1.0.1"
     "@nodelib/fs.walk" "^1.2.8"
@@ -1303,10 +1303,10 @@ prelude-ls@^1.2.1:
   resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz"
   integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
 
-prettier@3.0.2:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.2.tgz#78fcecd6d870551aa5547437cdae39d4701dca5b"
-  integrity sha512-o2YR9qtniXvwEZlOKbveKfDQVyqxbEIWn48Z8m3ZJjBjcCmUy3xZGIv+7AkaeuaTr6yPXJjwv07ZWlsWbEy1rQ==
+prettier@3.0.3:
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.3.tgz#432a51f7ba422d1469096c0fdc28e235db8f9643"
+  integrity sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==
 
 pretty-bytes@^5.6.0:
   version "5.6.0"
diff --git a/main/tests/data/csv-with-bom.csv b/main/tests/data/csv-with-bom.csv
new file mode 100644
index 000000000..bdb3bcc64
--- /dev/null
+++ b/main/tests/data/csv-with-bom.csv
@@ -0,0 +1,3 @@
+﻿ColA,ColB
+A1,B1
+A2,B2
\ No newline at end of file
diff --git a/main/tests/server/src/com/google/refine/importing/EncodingGuesserTests.java b/main/tests/server/src/com/google/refine/importing/EncodingGuesserTests.java
index 73d60f011..d5e1a060a 100644
--- a/main/tests/server/src/com/google/refine/importing/EncodingGuesserTests.java
+++ b/main/tests/server/src/com/google/refine/importing/EncodingGuesserTests.java
@@ -15,7 +15,7 @@ import com.google.refine.util.ParsingUtilities;
 
 public class EncodingGuesserTests {
 
-    // Guessing isn't as reliable for single-byte encodings, so we focus on a few multi-byte
+    // Guessing isn't as reliable for single-byte encodings, so we focus on a few multibyte
     // non-UTF8 encodings which are still in use (but <1% prevalence on web)
     static String[] ENCODINGS = {
             "big5",
@@ -25,7 +25,7 @@ public class EncodingGuesserTests {
     };
 
     private static File getTestDir() {
-        String dir = ClassLoader.getSystemResource(ENCODINGS[0] + ".html").getPath();
+        String dir = ClassLoader.getSystemResource(ENCODINGS[0] + ".txt").getPath();
         dir = dir.substring(0, dir.lastIndexOf('/'));
         return new File(dir);
     }
@@ -46,21 +46,29 @@ public class EncodingGuesserTests {
     public void testEncodingGuesser() throws IOException {
 
         for (String encoding : ENCODINGS) {
-            ImportingJob job = new ImportingJobStub();
-            ObjectNode config = job.getOrCreateDefaultConfig();
-            ObjectNode filesObj = ParsingUtilities.evaluateJsonStringToObjectNode(
-                    String.format("{ \"files\": [ {\"location\": \"%s.txt\"}]}", encoding));
-            JSONUtilities.safePut(config, "retrievalRecord", filesObj);
-
-            EncodingGuesser.guess(job);
-
-            ObjectNode retrievalRecord = job.getRetrievalRecord();
-            assertNotNull(retrievalRecord);
-            ArrayNode fileRecords = JSONUtilities.getArray(retrievalRecord, "files");
-            assertNotNull(fileRecords);
-            assertEquals(fileRecords.size(), 1);
-            ObjectNode record = JSONUtilities.getObjectElement(fileRecords, 0);
-            assertEquals(JSONUtilities.getString(record, "encoding", null).toLowerCase(), encoding);
+            checkEncoding(encoding + ".txt", encoding);
         }
+
+        checkEncoding("example-latin1.tsv", "windows-1252"); // close enough - these overlap a lot
+        checkEncoding("example-utf8.tsv", "utf-8");
+        checkEncoding("csv-with-bom.csv", "utf-8-bom");
+    }
+
+    private void checkEncoding(String filename, String encoding) throws IOException {
+        ImportingJob job = new ImportingJobStub();
+        ObjectNode config = job.getOrCreateDefaultConfig();
+        ObjectNode filesObj = ParsingUtilities.evaluateJsonStringToObjectNode(
+                String.format("{ \"files\": [ {\"location\": \"%s\"}]}", filename));
+        JSONUtilities.safePut(config, "retrievalRecord", filesObj);
+
+        EncodingGuesser.guess(job);
+
+        ObjectNode retrievalRecord = job.getRetrievalRecord();
+        assertNotNull(retrievalRecord);
+        ArrayNode fileRecords = JSONUtilities.getArray(retrievalRecord, "files");
+        assertNotNull(fileRecords);
+        assertEquals(fileRecords.size(), 1);
+        ObjectNode record = JSONUtilities.getObjectElement(fileRecords, 0);
+        assertEquals(JSONUtilities.getString(record, "encoding", null).toLowerCase(), encoding);
     }
 }
diff --git a/main/tests/server/src/com/google/refine/importing/ImportingUtilitiesTests.java b/main/tests/server/src/com/google/refine/importing/ImportingUtilitiesTests.java
index 19fc71ce8..0de1291b2 100644
--- a/main/tests/server/src/com/google/refine/importing/ImportingUtilitiesTests.java
+++ b/main/tests/server/src/com/google/refine/importing/ImportingUtilitiesTests.java
@@ -56,7 +56,6 @@ import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder;
 import org.apache.hc.client5.http.entity.mime.StringBody;
 import org.apache.hc.core5.http.ContentType;
 import org.apache.hc.core5.http.HttpEntity;
-import org.checkerframework.checker.units.qual.A;
 import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
@@ -110,7 +109,7 @@ public class ImportingUtilitiesTests extends ImporterTest {
         File dirA = new File(tempDir, "a");
         dirA.mkdir();
         File conflicting = new File(dirA, "dummy");
-        conflicting.createNewFile();
+        Assert.assertTrue(conflicting.createNewFile());
 
         File allocated = ImportingUtilities.allocateFile(dirA, ".././a/dummy");
         Assert.assertEquals(allocated, new File(dirA, "dummy-2"));