Update code checking actions,, add ESLint tests, and fix test race conditions #1323 (#1328)

This commit is contained in:
Jaifroid 2025-03-26 16:38:57 +00:00 committed by GitHub
parent 6d263537c1
commit 49d7fbc99d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
38 changed files with 3190 additions and 8103 deletions

View File

@ -76,6 +76,10 @@ jobs:
fi
exit $failed
- name: Run ESLint
run: |
npx eslint
- name: End-to-end tests on Chrome (Linux)
env:
GITHUB_ACTION: ${{ github.event_name }}
@ -181,6 +185,10 @@ jobs:
- name: Unit tests (Windows)
run: npm run test-unit
- name: Run ESLint
run: |
npx eslint
- name: End-to-end tests on Edge Chromium (Windows)
env:
GITHUB_ACTION: ${{ github.event_name }}

View File

@ -43,7 +43,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@ -54,7 +54,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
uses: github/codeql-action/autobuild@v3
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@ -68,4 +68,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3

View File

@ -85,8 +85,8 @@ _You must test your code yourself before asking for review, like this_:
* _You **must** test your fix in both "Restricted" and "ServiceWorker" modes_ (under Compatibility settings). You will be astonished the number of times a new contributor tells us
that their fix is working, but we discover they only applied the fix in one of these two modes. Don't be **that** contributor!
* Unit tests, which test for regressions with basic app functions, are run automatically with GitHub Actions on each PR and push to a PR. If one of these tests fails, you will want
to debug. First, see if you can also see the failure by running the tests with `npm test`, which should run the tests in all your installed browsers. To address any issues
identified, see [TESTS](./TESTS.md) so you can debug;
to debug. First, see if you can also see the failure by running the tests with `npm test`, which will run the tests in the NodeJS environment.
To address any issues identified, see [TESTS](./TESTS.md) so you can debug;
* End-to-end (e2e) tests are also run on GitHub Actions when you push to your PR. These test typical user actions in a headless browser. Tests are currently enabled for latest
Firefox, Edge, Chrome in Linux and Windows, and in IE Mode on Windows (this is the equivalent to testing on Internet Explorer 11). You can run these tests yourself in a
non-headless browser with `npm run tests-e2e-firefox`, `npm run tests-e2e-iemode`, etc. For more information, see [TESTS](./TESTS.md). For IE Mode, you will need to have the Edge

12
eslint.config.js Normal file
View File

@ -0,0 +1,12 @@
import { defineConfig } from 'eslint/config';
import globals from 'globals';
import js from '@eslint/js';
export default defineConfig([
{ files: ['**/*.{js,mjs,cjs}'] },
{ files: ['**/*.{js,mjs,cjs}'], languageOptions: { globals: globals.browser } },
{ files: ['**/*.{js,mjs,cjs}'], plugins: { js }, extends: ['js/recommended'] },
{ ignores: ['dist/**', 'emscripten/**', '**/xzdec*.js', '**/zstddec*.js', '**/webpHero*.js', '**/jquery*.js',
'**/bootstrap*.js', '**/require.js', '**/solid.js', '**/fontawesome.js', '**/replayWorker.js',
'**/*-wasm*.js', '**/*-asm*.js', 'tmp/**'] }
]);

View File

@ -1,4 +1,3 @@
/* eslint-disable quote-props, quotes, indent */
document.localeJson = {
"en": {
"translation": {

View File

@ -1,4 +1,3 @@
/* eslint-disable quote-props, quotes, indent */
document.localeJson = {
"es": {
"translation": {

View File

@ -1,4 +1,3 @@
/* eslint-disable quote-props, quotes, indent */
document.localeJson = {
"fr": {
"translation": {

11111
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -40,6 +40,7 @@
"@babel/core": "^7.26.0",
"@babel/preset-env": "^7.26.0",
"@babel/register": "^7.25.9",
"@eslint/js": "^9.23.0",
"@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
@ -49,18 +50,18 @@
"babel-plugin-polyfill-corejs3": "^0.7.1",
"chai": "^5.1.2",
"del-cli": "^5.0.0",
"eslint": "^8.42.0",
"eslint-config-standard": "^17.1.0",
"eslint": "^9.23.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-n": "^16.0.0",
"eslint-plugin-promise": "^6.1.1",
"globals": "^16.0.0",
"http-server": "^14.1.1",
"jsdom": "^25.0.1",
"mocha": "^10.8.2",
"nyc": "^17.1.0",
"rollup": "^4.5.0",
"rollup-plugin-copy": "^3.4.0",
"selenium-webdriver": "^4.11.1",
"selenium-webdriver": "^4.30.0",
"sinon": "^19.0.2",
"start-server-and-test": "^2.0.0",
"vite": "^6.2.3"

View File

@ -9,6 +9,9 @@ import terser from '@rollup/plugin-terser';
import { minify } from 'terser';
// import styles from "@ironkinoko/rollup-plugin-styles";
/* global process */
/* eslint-disable no-unused-vars */
const config = {
// The entry point for the bundler
input: 'www/js/app.js',

View File

@ -25,8 +25,6 @@
/* global chrome */
/* eslint-disable prefer-const */
/**
* App version number - ENSURE IT MATCHES VALUE IN init.js
* DEV: Changing this will cause the browser to recognize that the Service Worker has changed, and it will
@ -541,7 +539,7 @@ function cacheAndReturnResponseForAsset (event, response) {
* Zimit requests may be for a range of bytes, in fact video (at least) is stored as a blob, so the appropriate response will just be a normal 200.
* @returns {Promise<Response>} A Promise for the Response, or rejects with the invalid message port data
*/
function fetchUrlFromZIM (urlObjectOrString, range, expectedHeaders) {
function fetchUrlFromZIM (urlObjectOrString, range/*, expectedHeaders*/) {
return new Promise(function (resolve, reject) {
var pathname = typeof urlObjectOrString === 'string' ? urlObjectOrString : urlObjectOrString.pathname;
// Note that titles may contain bare question marks or hashes, so we must use only the pathname without any URL parameters.

View File

@ -1,7 +1,7 @@
import { Builder } from 'selenium-webdriver';
import legacyRayCharles from '../../spec/legacy-ray_charles.e2e.spec.js';
/* eslint-disable camelcase */
/* global process */
// Input capabilities
const capabilities = {

View File

@ -4,7 +4,7 @@ import gutenbergRo from '../../spec/gutenberg_ro.e2e.spec.js';
import tonedear from '../../spec/tonedear.e2e.spec.js'
import paths from '../../paths.js';
/* eslint-disable camelcase */
/* global process */
// Input capabilities
const capabilities = {

View File

@ -5,7 +5,7 @@ import gutenbergRo from '../../spec/gutenberg_ro.e2e.spec.js';
import tonedearTests from '../../spec/tonedear.e2e.spec.js';
import paths from '../../paths.js';
/* eslint-disable camelcase */
/* global process */
async function loadChromiumDriver () {
const options = new Options();

View File

@ -3,7 +3,7 @@ import legacyRayCharles from '../../spec/legacy-ray_charles.e2e.spec.js';
import gutenbergRo from '../../spec/gutenberg_ro.e2e.spec.js';
import tonedear from '../../spec/tonedear.e2e.spec.js';
/* eslint-disable camelcase */
/* global process */
// Input capabilities
const capabilities = {

View File

@ -4,8 +4,6 @@ import legacyRayCharles from '../../spec/legacy-ray_charles.e2e.spec.js';
import gutenbergRo from '../../spec/gutenberg_ro.e2e.spec.js';
import tonedear from '../../spec/tonedear.e2e.spec.js';
/* eslint-disable camelcase */
async function loadIEModeDriver () {
const ieOptions = new Options();
ieOptions.setEdgeChromium(true);

View File

@ -3,7 +3,8 @@ import { Options } from 'selenium-webdriver/edge.js';
import legacyRayCharles from '../../spec/legacy-ray_charles.e2e.spec.js';
import gutenbergRo from '../../spec/gutenberg_ro.e2e.spec.js';
import tonedearTests from '../../spec/tonedear.e2e.spec.js';
/* eslint-disable camelcase */
/* global process */
async function loadMSEdgeDriver () {
const options = new Options();

View File

@ -5,13 +5,13 @@ import gutenbergRo from '../../spec/gutenberg_ro.e2e.spec.js';
import tonedearTests from '../../spec/tonedear.e2e.spec.js';
import paths from '../../paths.js';
/* eslint-disable camelcase */
/* global process */
async function loadFirefoxDriver () {
const options = new firefox.Options();
// Run it headless if the environment variable GITHUB_ACTIONS is set
if (process.env.GITHUB_ACTIONS) {
options.headless();
options.addArguments('--headless'); // Explicitly set headless mode
}
options.setPreference('browser.download.folderList', 2);
options.setPreference('browser.download.dir', paths.downloadDir);

View File

@ -1,7 +1,7 @@
import { Builder } from 'selenium-webdriver';
import legacyRayCharles from '../../spec/legacy-ray_charles.e2e.spec.js';
/* eslint-disable camelcase */
/* global process */
// Input capabilities
const capabilities = {

View File

@ -1,7 +1,8 @@
import { Builder } from 'selenium-webdriver';
import gutenbergRo from '../../spec/gutenberg_ro.e2e.spec.js';
import tonedear from '../../spec/tonedear.e2e.spec.js';
/* eslint-disable camelcase */
/* global process */
// Input capabilities
const capabilities = {

View File

@ -3,7 +3,7 @@ import legacyRayCharles from '../../spec/legacy-ray_charles.e2e.spec.js';
import gutenbergRo from '../../spec/gutenberg_ro.e2e.spec.js';
import tonedearTests from '../../spec/tonedear.e2e.spec.js';
/* eslint-disable camelcase */
/* global process */
// Input capabilities
const capabilities = {

View File

@ -26,8 +26,8 @@ import assert from 'assert';
import paths from '../paths.js';
import fs from 'fs';
/* eslint-disable camelcase */
/* global describe, it */
/* global describe, it, process */
/* eslint-disable no-unused-vars */
// Get the BrowserStack environment variable
const BROWSERSTACK = !!process.env.BROWSERSTACK_LOCAL_IDENTIFIER;

View File

@ -19,15 +19,15 @@
* You should have received a copy of the GNU General Public License
* along with Kiwix (file LICENSE-GPLv3.txt). If not, see <http://www.gnu.org/licenses/>
*/
// eslint-disable-next-line no-unused-vars
/* eslint-disable no-unused-vars */
/* global describe, it, process */
import { By, Key, WebDriver, until } from 'selenium-webdriver';
// import firefox from 'selenium-webdriver/firefox.js';
import assert from 'assert';
import paths from '../paths.js';
/* eslint-disable camelcase, one-var, prefer-const */
/* global describe, it */
// Get the BrowserStack environment variable
const BROWSERSTACK = !!process.env.BROWSERSTACK_LOCAL_IDENTIFIER;
// DEV: For local testing, use line below instead
@ -239,37 +239,50 @@ function runTests (driver, modes, keepDriver) {
}
// console.log('FilesLength outer: ' + filesLength);
// Switch to iframe and check that the index contains the specified article
// Wait until the iframe is present and available
await driver.wait(async function () {
const iframe = await driver.findElement(By.id('articleContent'));
return iframe !== null;
}, 6000, 'Iframe with id "articleContent" was not found');
// Switch to the iframe
await driver.switchTo().frame('articleContent');
// Wait until the index has loaded
// Wait until the index has loaded inside the iframe
await driver.wait(async function () {
const contentAvailable = await driver.executeScript('return document.getElementById("mw-content-text");');
return contentAvailable;
}, 6000);
// const articleLink = await driver.wait(until.elementLocated(By.xpath('/html/body/div/div/ul/li[77]/a[2]')));
// const text = await articleLink.getText();
let articleLink;
return contentAvailable !== null;
}, 6000, 'Content inside iframe did not load');
// Locate the article link and get its text
const text = await driver.wait(async function () {
articleLink = await driver.findElement(By.xpath('/html/body/div/div/ul/li[77]/a[2]'));
const articleLink = await driver.findElement(By.xpath('/html/body/div/div/ul/li[77]/a[2]'));
return await articleLink.getText();
}, 6000);
// const articleLink = await driver.findElement(By.linkText('This Little Girl of Mine'));
// Assert that the text matches the expected value
assert.equal('This Little Girl of Mine', text);
// Re-locate the article link just before interacting with it to avoid stale reference
const articleLink = await driver.findElement(By.xpath('/html/body/div/div/ul/li[77]/a[2]'));
// Scroll the element into view and navigate to it
await driver.executeScript('var el=arguments[0]; el.scrollIntoView(true); setTimeout(function () {el.click();}, 50); return el.offsetParent;', articleLink);
// Pause for 2 seconds to allow article to load
// Pause for 2 seconds to allow the article to load
await driver.sleep(2000);
// Check the content of the loaded article
let elementText = '';
try {
// Find the mwYw element in JavaScript and get its content
elementText = await driver.executeScript('return document.getElementById("mwYw").textContent;');
} catch (e) {
// We probably got a NoSuchFrameError on Safari, so try selecting it with a different method
// Handle cases where the frame or element is not accessible
await driver.switchTo().defaultContent();
elementText = await driver.executeScript('var iframeDoc = document.getElementById("articleContent").contentDocument; return iframeDoc.getElementById("mwYw").textContent;');
}
// console.log('Element text: ' + elementText);
// Check that the article title is correct
// Assert that the article content matches the expected value
assert.equal('Instrumentation by the Ray Charles Orchestra', elementText);
await driver.switchTo().defaultContent();
});

View File

@ -1,8 +1,11 @@
/* eslint-disable no-undef */
/* eslint-disable no-unused-vars */
import { By, until } from 'selenium-webdriver';
import assert from 'assert';
import paths from '../paths.js';
/* global describe, it, process */
const BROWSERSTACK = !!process.env.BROWSERSTACK_LOCAL_IDENTIFIER;
const port = BROWSERSTACK ? '8099' : '8080';
const tonedearBaseFile = BROWSERSTACK ? '/tests/zims/tonedear/tonedear.com_en_2024-09.zim' : paths.tonedearBaseFile;

View File

@ -1,4 +1,3 @@
/* eslint-disable no-useless-constructor */
/**
* init.js : Global configuration of the app
* This file handles the dependencies between javascript libraries, for the unit tests
@ -24,10 +23,9 @@
'use strict';
/* global webpHero */
/* global webpHero, global */
// Define global params needed for tests to run on existing app code
// eslint-disable-next-line no-unused-vars
var params = {};
// We need to turn off source verification so that the test files can be loaded normally without interruption
params['sourceVerification'] = false;

View File

@ -1,5 +1,5 @@
/* eslint-disable no-prototype-builtins */
/* eslint-disable import/no-named-default */
/**
* Mocha test environment setup
*
@ -110,6 +110,7 @@ class Worker {
this.onerror = null;
}
// eslint-disable-next-line no-unused-vars
postMessage (msg) {
// Implement any worker simulation logic here if needed
if (this.onmessage) {

View File

@ -1,4 +1,3 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
// Converted Test from test.js file
import { expect } from 'chai';

View File

@ -497,7 +497,7 @@ document.getElementById('libzimModeSelect').addEventListener('change', function
window.location.reload();
});
document.getElementById('useLibzim').addEventListener('click', function (e) {
document.getElementById('useLibzim').addEventListener('click', function () {
settingsStore.setItem('useLibzim', !params.useLibzim);
window.location.reload();
});
@ -533,7 +533,8 @@ document.getElementById('serviceworkerLocalModeRadio').addEventListener('click',
});
// Source verification is only makes sense in SW mode as doing the same in jQuery mode is redundant.
document.getElementById('enableSourceVerificationCheckBox').style.display = params.contentInjectionMode === ('serviceworker' || 'serviceworkerlocal') ? 'block' : 'none';
document.getElementById('enableSourceVerificationCheckBox').style.display = (params.contentInjectionMode === 'serviceworker' ||
params.contentInjectionMode === 'serviceworkerlocal') ? 'block' : 'none';
document.getElementById('enableSourceVerification').addEventListener('change', function () {
params.sourceVerification = this.checked;
@ -1052,9 +1053,10 @@ async function handleMessageChannelByLibzim (event) {
// We have a redirect to follow
// this is still a bit flawed, as we do not check if it's a redirect or the file doesn't exist
// We have no way to know if the file exists or not, so we have to assume it does and its just a redirect
// eslint-disable-next-line no-unused-vars
const dirEntry = await new Promise((resolve, _reject) => selectedArchive.getMainPageDirEntry((value) => resolve(value)));
if (dirEntry.redirect) {
// eslint-disable-next-line no-unused-vars
const redirect = await new Promise((resolve, _reject) => selectedArchive.resolveRedirect(dirEntry, (v) => resolve(v)));
const ret = await selectedArchive.callLibzimWorker({ action: 'getEntryByPath', path: redirect.namespace + '/' + redirect.url })
const message = { action: 'giveContent', title: title, content: ret.content, mimetype: ret.mimetype };
@ -1069,6 +1071,7 @@ async function handleMessageChannelByLibzim (event) {
} catch (error) {
const message = { action: 'giveContent', title: title, content: new Uint8Array(), mimetype: '' };
messagePort.postMessage(message);
console.error('Error while handling messageChannel', error);
}
}
@ -1280,6 +1283,7 @@ function isMessageChannelAvailable () {
var dummyMessageChannel = new MessageChannel();
if (dummyMessageChannel) return true;
} catch (e) {
console.warn(e);
return false;
}
return false;
@ -1791,7 +1795,7 @@ async function handleFileDrop (packet) {
}
const btnLibrary = document.getElementById('btnLibrary');
btnLibrary.addEventListener('click', function (e) {
btnLibrary.addEventListener('click', function () {
const libraryContent = document.getElementById('libraryContent');
const libraryIframe = libraryContent.contentWindow.document.getElementById('libraryIframe');
uiUtil.tabTransitionToSection('library', params.showUIAnimations);
@ -2747,6 +2751,7 @@ function displayArticleContentInIframe (dirEntry, htmlArticle) {
// If we couldn't get it, reconstruct it from the archive's zimitPrefix
zimitPrefix = zimitPrefix ? zimitPrefix[1] : selectedArchive.zimitPrefix.replace(/^\w\/([^/]+).*/, '$1');
zimitPrefix = (dirEntry.namespace === 'C' ? 'A/' : '') + zimitPrefix;
// eslint-disable-next-line no-unused-vars
htmlArticle = htmlArticle.replace(regexpZimitHtmlLinks, function (match, blockStart, equals, quote, relAssetUrl, blockClose) {
var newBlock = match;
var assetUrl = relAssetUrl;

View File

@ -250,6 +250,7 @@ function getBestAvailableStorageAPI () {
}
} catch (e) {
localStorageTest = false;
console.warn('localStorage is not available: ' + e);
}
document.cookie = 'tempKiwixCookieTest=working; expires=Fri, 31 Dec 9999 23:59:59 GMT; SameSite=Strict';
var kiwixCookieTest = /tempKiwixCookieTest=working/.test(document.cookie);

View File

@ -87,6 +87,7 @@ StorageFirefoxOS.prototype.scanForArchives = function () {
* @param path Path where to look for files
* @return {DOMCursor} Cursor of files found in given path
*/
// eslint-disable-next-line no-unused-vars
StorageFirefoxOS.prototype.enumerate = function (path) {
return this._storage.enumerate();
};
@ -342,6 +343,7 @@ async function handleFolderOrFileDropViaWebkit (event) {
*/
async function getFilesFromReader (reader) {
const files = [];
// eslint-disable-next-line no-unused-vars
const promise = new Promise(function (resolve, _reject) {
reader.readEntries(function (entries) {
resolve(entries);
@ -352,6 +354,7 @@ async function getFilesFromReader (reader) {
for (let index = 0; index < entries.length; index++) {
const fileOrDir = entries[index];
if (fileOrDir.isFile) {
// eslint-disable-next-line no-unused-vars
const filePromise = await new Promise(function (resolve, _reject) {
fileOrDir.file(function (file) {
resolve(file);

View File

@ -20,7 +20,7 @@
* along with Kiwix (file LICENSE-GPLv3.txt). If not, see <http://www.gnu.org/licenses/>
*/
/* globals params, appstate, caches, assetsCache */
/* globals params, appstate, assetsCache */
'use strict';
import settingsStore from './settingsStore.js';
@ -68,7 +68,7 @@ function test (callback) {
var item = window.localStorage.length;
assetsCache.capability = assetsCache.capability + '|localStorage';
} catch (err) {
console.log('localStorage is not supported');
console.warn('localStorage is not supported', err);
}
}
console.log('Setting storage type to ' + assetsCache.capability.match(/^[^|]+/)[0]);
@ -188,7 +188,7 @@ function idxDB (keyOrCommand, valueOrCallback, callback) {
// Open (or create) the database
var open = indexedDB.open(CACHEIDB, 1);
open.onerror = function (e) {
open.onerror = function () {
// Suppress error reporting if testing (older versions of Firefox support indexedDB but cannot use it with
// the file:// protocol, so will report an error)
if (assetsCache.capability !== 'test') {
@ -228,14 +228,14 @@ function idxDB (keyOrCommand, valueOrCallback, callback) {
processData = value !== null ? store.put(value, keyOrCommand) : store.get(keyOrCommand);
}
// Call the callback with the result
processData.onsuccess = function (e) {
processData.onsuccess = function () {
if (keyOrCommand === 'delete') {
rtnFn(true);
} else {
rtnFn(processData.result);
}
};
processData.onerror = function (e) {
processData.onerror = function () {
console.error('IndexedDB command failed: ' + processData.error);
rtnFn(false);
};
@ -579,6 +579,7 @@ function clear (items, callback) {
result = 'assetsCache';
}
// Delete and reinitialize assetsCache
// eslint-disable-next-line no-global-assign
assetsCache = new Map();
assetsCache.capability = capability;
// Loose test here ensures we clear localStorage even if it wasn't being used in this session

View File

@ -31,8 +31,7 @@ import translateUI from './translateUI.js';
* @returns {boolean} True if the browser can execute required code in the library iframe
*/
function canExecuteCode () {
try {
// eslint-disable-next-line no-new-func
try {
Function('try{}catch{}')();
return true;
} catch (error) {

View File

@ -464,7 +464,7 @@ function isSafari () {
* @param {Document} doc The doucment on which to operate
*/
function addEventListenersToPopoverIcons (anchor, popover, doc) {
const breakout = function (e) {
const breakout = function () {
// Adding the newcontainer property to the anchor will be cauught by the filterClickEvent function and will open in new tab
anchor.newcontainer = true;
anchor.click();

View File

@ -40,6 +40,7 @@ function getBestAvailableStorageAPI () {
}
} catch (e) {
localStorageTest = false;
console.warn('localStorage not supported in this context', e);
}
// Now test for document.cookie API support
document.cookie = 'tempKiwixCookieTest=working; expires=Fri, 31 Dec 9999 23:59:59 GMT; SameSite=Strict';

View File

@ -22,7 +22,6 @@
'use strict';
/* eslint-disable indent */
/* global webpMachine, webpHero, params */
import util from './util.js';
@ -576,6 +575,8 @@ function displayFileDownloadAlert (title, download, contentType, content) {
window.navigator.msSaveBlob(blob, filename);
e.preventDefault();
});
} else {
console.error('Error downloading file: ' + err);
}
}
spinnerDisplay(false);

View File

@ -24,8 +24,6 @@
'use strict';
/* eslint-disable eqeqeq */
var utf8 = {};
/**

View File

@ -22,9 +22,6 @@
'use strict';
/* eslint-disable indent */
/* eslint-disable one-var */
var regExpFindStringParts = /(?:^|.+?)(?:[\s$£€\uFFE5^+=`~<>{}[\]|\u3000-\u303F!-#%-\x2A,-/:;\x3F@\x5B-\x5D_\x7B}\u00A1\u00A7\u00AB\u00B6\u00B7\u00BB\u00BF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u0AF0\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166D\u166E\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2010-\u2027\u2030-\u2043\u2045-\u2051\u2053-\u205E\u207D\u207E\u208D\u208E\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E3B\u3001-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]+|$)/g;
/**

View File

@ -677,7 +677,8 @@ ZIMArchive.prototype.getMetadata = function (key, callback) {
ZIMArchive.prototype.addMetadataToZIMFile = function (key) {
var that = this;
var lcaseKey = key.toLocaleLowerCase();
return new Promise(function (resolve, reject) {
// eslint-disable-next-line no-unused-vars
return new Promise(function (resolve, _reject) {
that.getMetadata(key, function (data) {
data = data || '';
that[lcaseKey] = data;