Converted Q Unit tests to Mocha Tests (#1301)

* Added Files

* Fixed the init file

Signed-off-by: THEBOSS0369 <anujkumsharma9876@gmail.com>

* Trying again

* Adding coverage folders in gitignore

* Fixed fallback errors & some grammatical checks

Signed-off-by: THEBOSS0369 <anujkumsharma9876@gmail.com>

* Added unit test coverage

Signed-off-by: THEBOSS0369 <anujkumsharma9876@gmail.com>

* Revert "Added unit test coverage"

This reverts commit 444e215d79665da1cb8631b9268bdea1f8e701d8.

* Fixing merge conflicts

Signed-off-by: THEBOSS0369 <anujkumsharma9876@gmail.com>

* Integrate tests into workflows

Signed-off-by: THEBOSS0369 <anujkumsharma9876@gmail.com>

* removing unit-watch

Signed-off-by: THEBOSS0369 <anujkumsharma9876@gmail.com>

---------

Signed-off-by: THEBOSS0369 <anujkumsharma9876@gmail.com>
This commit is contained in:
Anuj Kumar Sharma 2025-03-17 18:27:36 +05:30 committed by GitHub
parent c628aa64b6
commit b3c404451b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 3075 additions and 5118 deletions

View File

@ -52,7 +52,7 @@ jobs:
run: npm ci run: npm ci
- name: Unit tests (Linux) - name: Unit tests (Linux)
run: npm run test-unit-browsers run: npm run test-unit
- name: Test a full build of the app - name: Test a full build of the app
run: npm run build run: npm run build
@ -179,17 +179,7 @@ jobs:
run: npm run build-min run: npm run build-min
- name: Unit tests (Windows) - name: Unit tests (Windows)
run: | run: npm run test-unit
$AggregateExitCode = 0
npm run test-unit-edge
$AggregateExitCode += $LastExitCode
npm run test-unit-firefox
$AggregateExitCode += $LastExitCode
if ($AggregateExitCode -ne 0) {
echo " "
Write-Error "Number of failed tests: $AggregateExitCode"
}
exit $AggregateExitCode
- name: End-to-end tests on Edge Chromium (Windows) - name: End-to-end tests on Edge Chromium (Windows)
env: env:

2
.gitignore vendored
View File

@ -36,6 +36,8 @@ intermediate
publish publish
.idea .idea
nbproject/private nbproject/private
.nyc_output
coverage
# build script local files # build script local files
build/buildinfo.properties build/buildinfo.properties

11
.mocharc.json Normal file
View File

@ -0,0 +1,11 @@
{
"extension": ["js"],
"spec": "tests/unit/**/*.test.js",
"timeout": 5000,
"recursive": true,
"exit": true,
"require": [
"@babel/register",
"./tests/unit/setup.js"
]
}

20
.nycrc Normal file
View File

@ -0,0 +1,20 @@
{
"all": true,
"include": [
"src/**/*.js"
],
"exclude": [
"**/*.test.js",
"tests/**",
"node_modules/**"
],
"reporter": [
"text",
"lcov"
],
"check-coverage": true,
"branches": 70,
"lines": 70,
"functions": 70,
"statements": 70
}

7123
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -14,12 +14,9 @@
"prebuild-src": "del-cli dist", "prebuild-src": "del-cli dist",
"build-src": "rollup --config --file dist/www/js/bundle.js", "build-src": "rollup --config --file dist/www/js/bundle.js",
"del-dist": "del-cli dist", "del-dist": "del-cli dist",
"test": "testcafe all ./tests/unit/initTestCafe.js --app \"http-server --silent -p 8080 .\"", "test": "mocha --experimental-modules --node-option experimental-specifier-resolution=node --require @babel/register --require tests/unit/setup.js 'tests/unit/spec/**/*.test.js'",
"test-unit-browsers": "testcafe firefox:headless,chrome:headless,edge:headless ./tests/unit/initTestCafe.js --app \"http-server --silent -p 8080 .\"", "test-unit": "mocha --experimental-modules --node-option experimental-specifier-resolution=node --require @babel/register --require tests/unit/setup.js 'tests/unit/spec/**/*.test.js'",
"test-unit-firefox": "testcafe firefox:headless ./tests/unit/initTestCafe.js --app \"http-server --silent -p 8080 .\"", "test-unit-coverage": "nyc --experimental-modules --node-option experimental-specifier-resolution=node mocha --require @babel/register --require tests/unit/setup.js 'tests/unit/spec/**/*.test.js'",
"test-unit-chrome": "testcafe chrome:headless ./tests/unit/initTestCafe.js --app \"http-server --silent -p 8080 .\"",
"test-unit-edge": "testcafe edge:headless ./tests/unit/initTestCafe.js --app \"http-server --silent -p 8080 .\"",
"test-unit-mac": "testcafe safari ./tests/unit/initTestCafe.js --app \"http-server --silent -p 8080 .\"",
"web-server": "http-server -p 8080 .", "web-server": "http-server -p 8080 .",
"test-e2e": "npm run test-e2e-firefox && npm run test-e2e-chrome && npm run test-e2e-edge && npm run test-e2e-iemode", "test-e2e": "npm run test-e2e-firefox && npm run test-e2e-chrome && npm run test-e2e-edge && npm run test-e2e-iemode",
"test-e2e-firefox": "npx start-server-and-test 'http-server --silent' 8080 'npx mocha ./tests/e2e/runners/firefox/firefox.e2e.runner.js'", "test-e2e-firefox": "npx start-server-and-test 'http-server --silent' 8080 'npx mocha ./tests/e2e/runners/firefox/firefox.e2e.runner.js'",
@ -35,12 +32,14 @@
"@types/fs-extra": "^9.0.11", "@types/fs-extra": "^9.0.11",
"bootstrap": "^4.6.2", "bootstrap": "^4.6.2",
"core-js": "3.30.2", "core-js": "3.30.2",
"jquery": "^3.7.0" "jquery": "^3.7.0",
"xhr2": "^0.2.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/cli": "^7.21.5", "@babel/cli": "^7.21.5",
"@babel/core": "^7.21.5", "@babel/core": "^7.26.0",
"@babel/preset-env": "^7.21.5", "@babel/preset-env": "^7.26.0",
"@babel/register": "^7.25.9",
"@rollup/plugin-babel": "^6.0.4", "@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-node-resolve": "^15.2.3",
@ -48,6 +47,7 @@
"@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-terser": "^0.4.4",
"@vitejs/plugin-legacy": "^6.0.2", "@vitejs/plugin-legacy": "^6.0.2",
"babel-plugin-polyfill-corejs3": "^0.7.1", "babel-plugin-polyfill-corejs3": "^0.7.1",
"chai": "^5.1.2",
"del-cli": "^5.0.0", "del-cli": "^5.0.0",
"eslint": "^8.42.0", "eslint": "^8.42.0",
"eslint-config-standard": "^17.1.0", "eslint-config-standard": "^17.1.0",
@ -55,13 +55,15 @@
"eslint-plugin-n": "^16.0.0", "eslint-plugin-n": "^16.0.0",
"eslint-plugin-promise": "^6.1.1", "eslint-plugin-promise": "^6.1.1",
"http-server": "^14.1.1", "http-server": "^14.1.1",
"mocha": "^10.2.0", "jsdom": "^25.0.1",
"mocha": "^10.8.2",
"nyc": "^17.1.0",
"qunit": "^2.19.4", "qunit": "^2.19.4",
"rollup": "^4.5.0", "rollup": "^4.5.0",
"rollup-plugin-copy": "^3.4.0", "rollup-plugin-copy": "^3.4.0",
"selenium-webdriver": "^4.11.1", "selenium-webdriver": "^4.11.1",
"sinon": "^19.0.2",
"start-server-and-test": "^2.0.0", "start-server-and-test": "^2.0.0",
"testcafe": "^3.0.0",
"vite": "^6.2.2" "vite": "^6.2.2"
} }
} }

View File

@ -1,13 +0,0 @@
/* global fixture, test */
import { Selector } from 'testcafe'; // first import testcafe selectors
fixture`Start QUnit tests`// declare the fixture
.page`http://localhost:8080/tests/unit/`; // specify the start page
// then create a test and place your code within it
test('Check for success', async t => {
await t
// Use the assertion to check if actual header text equals expected text
.expect(Selector('#qunit-testresult-display').innerText, { timeout: 10000 }).match(/0\s+failed.+?0\s+skipped.+?0\s+todo/, { timeout: 7000 });
});

View File

@ -1,5 +1,6 @@
/* eslint-disable no-useless-constructor */
/** /**
* init.js : Configuration for the library require.js * init.js : Global configuration of the app
* This file handles the dependencies between javascript libraries, for the unit tests * This file handles the dependencies between javascript libraries, for the unit tests
* *
* Copyright 2013-2014 Mossroy and contributors * Copyright 2013-2014 Mossroy and contributors
@ -34,6 +35,41 @@ params['sourceVerification'] = false;
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
var webpMachine = false; var webpMachine = false;
// Create a mock Image class for Node.js environment
if (typeof window === 'undefined') {
// We're in Node.js
global.Image = class Image {
constructor () {
this.height = 0;
setTimeout(() => {
// Simulate successful WebP support
this.height = 2;
if (typeof this.onload === 'function') {
this.onload();
}
}, 0);
}
};
global.document = {
head: {
appendChild: function () {} // Mock appendChild
},
createElement: function () {
return {
onload: null,
src: ''
};
}
};
global.webpHero = {
WebpMachine: class WebpMachine {
constructor () {}
}
};
}
// We use a self-invoking function here to avoid defining unnecessary global functions and variables // We use a self-invoking function here to avoid defining unnecessary global functions and variables
(function (callback) { (function (callback) {
// Tests for native WebP support // Tests for native WebP support

227
tests/unit/setup.js Normal file
View File

@ -0,0 +1,227 @@
/* eslint-disable no-prototype-builtins */
/* eslint-disable import/no-named-default */
/**
* Mocha test environment setup
*
* This file configures the test environment for Mocha tests,
* including any global setup, hooks or configuration needed
* before running the tests.
*/
import { JSDOM } from 'jsdom';
import { expect, assert } from 'chai';
import * as sinon from 'sinon';
import { default as jQuery } from 'jquery';
// Initialize params before anything else
globalThis.params = {
sourceVerification: false
};
// Create JSDOM instance
const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>', {
url: 'http://localhost',
referrer: 'http://localhost',
contentType: 'text/html',
includeNodeLocations: true,
storageQuota: 10000000,
pretendToBeVisual: true,
resources: 'usable'
});
// Set up the global environment properly
const { window } = dom;
const { document } = window;
// Define self
globalThis.self = globalThis;
window.self = window;
// Define Image class before setting up window
class Image {
constructor () {
this.height = 0;
this.onload = null;
this.onerror = null;
setTimeout(() => {
this.height = 2;
if (typeof this.onload === 'function') {
this.onload();
}
}, 0);
}
set src (value) {
this._src = value;
if (this.onload) {
setTimeout(this.onload, 0);
}
}
get src () {
return this._src;
}
}
// Copy window properties to global
Object.defineProperty(globalThis, 'window', {
value: window,
writable: true,
enumerable: true,
configurable: true
});
Object.defineProperty(globalThis, 'document', {
value: document,
writable: true,
enumerable: true,
configurable: true
});
// Make params available on window as well
Object.defineProperty(window, 'params', {
value: globalThis.params,
writable: true,
enumerable: true,
configurable: true
});
// Add Image to both global and window
Object.defineProperty(globalThis, 'Image', {
value: Image,
writable: true,
enumerable: true,
configurable: true
});
Object.defineProperty(window, 'Image', {
value: Image,
writable: true,
enumerable: true,
configurable: true
});
// Mock Worker if needed for xzdec_wrapper.js
class Worker {
constructor (stringUrl) {
this.url = stringUrl;
this.onmessage = null;
this.onerror = null;
}
postMessage (msg) {
// Implement any worker simulation logic here if needed
if (this.onmessage) {
setTimeout(() => {
this.onmessage({ data: {} });
}, 0);
}
}
terminate () {
// If needed then cleanup logic will be added
}
}
globalThis.Worker = Worker;
window.Worker = Worker;
// Add other required globals
globalThis.HTMLElement = window.HTMLElement;
globalThis.CustomEvent = window.CustomEvent;
globalThis.Event = window.Event;
globalThis.Node = window.Node;
globalThis.location = window.location;
globalThis.history = window.history;
globalThis.Element = window.Element;
// Mock webpHero
globalThis.webpHero = {
WebpMachine: class WebpMachine {
constructor (options) {
this.options = options;
}
}
};
// Setup jQuery
const $ = jQuery(window);
Object.defineProperty(globalThis, '$', {
value: $,
writable: true,
enumerable: true,
configurable: true
});
Object.defineProperty(globalThis, 'jQuery', {
value: $,
writable: true,
enumerable: true,
configurable: true
});
// Setup test utilities
globalThis.expect = expect;
globalThis.assert = assert;
globalThis.sinon = sinon;
// Add custom test helpers
globalThis.createTestElement = (html) => {
const div = document.createElement('div');
div.innerHTML = html;
return div.firstElementChild;
};
// Clean up function for tests
globalThis.cleanupDOM = () => {
document.body.innerHTML = '';
};
// Mock createElement for script tags
const originalCreateElement = document.createElement.bind(document);
document.createElement = (tagName) => {
const element = originalCreateElement(tagName);
if (tagName.toLowerCase() === 'script') {
setTimeout(() => {
if (element.onload) {
element.onload();
}
}, 0);
}
return element;
};
// Add necessary DOM APIs that might be missing
if (!window.requestAnimationFrame) {
window.requestAnimationFrame = function (callback) {
return setTimeout(callback, 0);
};
}
if (!window.cancelAnimationFrame) {
window.cancelAnimationFrame = function (id) {
clearTimeout(id);
};
}
// Set up localStorage mock
if (!window.localStorage) {
window.localStorage = {
getItem: function (key) {
return this[key] || null;
},
setItem: function (key, value) {
this[key] = value.toString();
},
removeItem: function (key) {
delete this[key];
},
clear: function () {
for (const key in this) {
if (this.hasOwnProperty(key)) {
delete this[key];
}
}
}
};
}

View File

@ -0,0 +1,319 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
// Converted Test from test.js file
import { expect } from 'chai';
import { promises as fs } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import '../js/init.js';
import zimArchive from '../../../www/js/lib/zimArchive.js';
import zimDirEntry from '../../../www/js/lib/zimDirEntry.js';
import util from '../../../www/js/lib/util.js';
import uiUtil from '../../../www/js/lib/uiUtil.js';
import utf8 from '../../../www/js/lib/utf8.js';
let localZimArchive;
/**
* Read a file and return it as a Blob
*
* @param {String} filePath Path to the file
* @param {String} name Name to give to the Blob instance
* @returns {Promise<Blob>} A Promise for the Blob
*/
async function readFileAsBlob (filePath, name) {
try {
const buffer = await fs.readFile(filePath);
const blob = new Blob([buffer], { type: 'application/octet-stream' });
blob.name = name;
return blob;
} catch (error) {
console.error('Error reading file:', filePath, error);
throw error;
}
}
// Get the directory of the current module
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Load test files before running tests
let zimArchiveFiles = [];
before(async function () {
const testFolder = join(__dirname, '../../../tests/zims/legacy-ray-charles');
const splitBlobs = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o'].map(function (c) {
const filename = 'wikipedia_en_ray_charles_2015-06.zima' + c;
return readFileAsBlob(join(testFolder, filename), filename);
});
zimArchiveFiles = await Promise.all(splitBlobs);
return new Promise((resolve) => {
localZimArchive = new zimArchive.ZIMArchive(zimArchiveFiles, null, function () {
resolve();
});
});
});
describe('Environment', function () {
it('Configure Mocha Test', function () {
expect('test').to.equal('test');
});
it('Load archive files', function () {
expect(zimArchiveFiles && zimArchiveFiles[0] && zimArchiveFiles[0].size > 0).to.be.true;
});
});
describe('Utils', function () {
it('Correctly read an IEEE_754 float from 4 bytes', function () {
const byteArray = new Uint8Array([194, 237, 64, 0]);
const float = util.readFloatFrom4Bytes(byteArray, 0);
expect(float).to.equal(-118.625);
});
it('Handle upper/lower case variation', function () {
const testCases = [
{ input: 'téléphone', expected: 'Téléphone', description: 'The first letter should be uppercase' },
{ input: 'Paris', expected: 'paris', description: 'The first letter should be lowercase' },
{ input: 'le Couvre-chef Est sur le porte-manteaux', expected: 'Le Couvre-Chef Est Sur Le Porte-Manteaux', description: 'The first letter of every word should be uppercase' },
{ input: 'épée', expected: 'Épée', description: 'The first letter should be uppercase (with accent)' },
{ input: '$¥€"«xριστός» †¡Ἀνέστη!"', expected: '$¥€"«Xριστός» †¡ἀνέστη!"', description: 'First non-punctuation/non-currency Unicode letter should be uppercase, second (with breath mark) lowercase' },
{ input: 'Καλά Νερά Μαγνησία žižek', expected: 'ΚΑΛΆ ΝΕΡΆ ΜΑΓΝΗΣΊΑ ŽIŽEK', options: 'full', description: 'All Unicode letters should be uppercase' }
];
testCases.forEach(({ input, expected, options, description }) => {
const results = options
? util.allCaseFirstLetters(input, options)
: util.allCaseFirstLetters(input);
expect(results, description).to.be.an('array');
expect(results.some(result => result === expected), description).to.be.true;
});
});
it('Remove parameters from URLs', function () {
const baseUrl = "A/Che cosa e l'amore?.html";
const testUrls = [
"A/Che%20cosa%20e%20l'amore%3F.html?param1=toto&param2=titi",
"A/Che%20cosa%20e%20l'amore%3F.html?param1=toto&param2=titi#anchor",
"A/Che%20cosa%20e%20l'amore%3F.html#anchor"
];
testUrls.forEach(testUrl => {
const result = uiUtil.removeUrlParameters(testUrl);
// Compare normalized strings (removing special characters)
const normalizedResult = decodeURIComponent(result).normalize('NFD').replace(/[\u0300-\u036f]/g, '');
const normalizedExpected = baseUrl.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
expect(normalizedResult).to.equal(normalizedExpected);
});
});
});
describe('ZIM initialization', function () {
it('Set archive as ready', function () {
expect(localZimArchive.isReady()).to.be.true;
});
});
describe('ZIM metadata', function () {
it('Read ZIM language', async function () {
const language = await new Promise(resolve => {
localZimArchive.getMetadata('Language', resolve);
});
expect(language).to.equal('eng');
});
it('Handle missing metadata', async function () {
const result = await new Promise(resolve => {
localZimArchive.getMetadata('zzz', resolve);
});
expect(result).to.be.undefined;
});
});
describe('ZIM directory entry search and read', function () {
it('DirEntry.fromStringId "A Fool for You"', async function () {
const aFoolForYouDirEntry = zimDirEntry.DirEntry.fromStringId(
localZimArchive.file,
'5856|7|A|0|2|A_Fool_for_You.html|A Fool for You|false|undefined'
);
const htmlArticle = await new Promise(resolve => {
localZimArchive.readUtf8File(aFoolForYouDirEntry, (_, article) => resolve(article));
});
expect(htmlArticle).to.have.length.above(0);
const normalizedArticle = htmlArticle.replace(/[\r\n]/g, ' ');
expect(normalizedArticle).to.match(/^.*<h1[^>]*>A Fool for You<\/h1>/);
});
it('FindDirEntriesWithPrefix "A"', async function () {
const dirEntryList = await new Promise(resolve => {
localZimArchive.findDirEntriesWithPrefix({ prefix: 'A', size: 5 }, resolve, true);
});
expect(dirEntryList).to.have.length(5);
expect(dirEntryList[0].getTitleOrUrl()).to.equal('A Fool for You');
});
it('FindDirEntriesWithPrefix "a"', async function () {
const dirEntryList = await new Promise(resolve => {
localZimArchive.findDirEntriesWithPrefix({ prefix: 'a', size: 5 }, resolve, true);
});
expect(dirEntryList).to.have.length(5);
expect(dirEntryList[0].getTitleOrUrl()).to.equal('A Fool for You');
});
it('FindDirEntriesWithPrefix "blues brothers"', async function () {
const dirEntryList = await new Promise(resolve => {
localZimArchive.findDirEntriesWithPrefix({ prefix: 'blues brothers', size: 5 }, resolve, true);
});
expect(dirEntryList).to.have.length(3);
expect(dirEntryList[0].getTitleOrUrl()).to.equal('Blues Brothers (film)');
});
it('Redirect article "(The Night Time Is) The Right Time" to "Night Time Is the Right Time"', async function () {
const dirEntry = await localZimArchive.getDirEntryByPath('A/(The_Night_Time_Is)_The_Right_Time.html');
expect(dirEntry).to.not.be.null;
expect(dirEntry.isRedirect()).to.be.true;
expect(dirEntry.getTitleOrUrl()).to.equal('(The Night Time Is) The Right Time');
const resolvedDirEntry = await new Promise(resolve => {
localZimArchive.resolveRedirect(dirEntry, resolve);
});
expect(resolvedDirEntry).to.not.be.null;
expect(resolvedDirEntry.isRedirect()).to.be.false;
expect(resolvedDirEntry.getTitleOrUrl()).to.equal('Night Time Is the Right Time');
});
it('Redirect article "Raelettes" to "The Raelettes"', async function () {
const dirEntry = await localZimArchive.getDirEntryByPath('A/Raelettes.html');
expect(dirEntry).to.not.be.null;
expect(dirEntry.isRedirect()).to.be.true;
expect(dirEntry.getTitleOrUrl()).to.equal('Raelettes');
const resolvedDirEntry = await new Promise(resolve => {
localZimArchive.resolveRedirect(dirEntry, resolve);
});
expect(resolvedDirEntry).to.not.be.null;
expect(resolvedDirEntry.isRedirect()).to.be.false;
expect(resolvedDirEntry.getTitleOrUrl()).to.equal('The Raelettes');
});
it('Redirect article "Bein Green" to "Bein\' Green"', async function () {
const dirEntry = await localZimArchive.getDirEntryByPath('A/Bein_Green.html');
expect(dirEntry).to.not.be.null;
expect(dirEntry.isRedirect()).to.be.true;
expect(dirEntry.getTitleOrUrl()).to.equal('Bein Green');
const resolvedDirEntry = await new Promise(resolve => {
localZimArchive.resolveRedirect(dirEntry, resolve);
});
expect(resolvedDirEntry).to.not.be.null;
expect(resolvedDirEntry.isRedirect()).to.be.false;
expect(resolvedDirEntry.getTitleOrUrl()).to.equal("Bein' Green");
});
it('Redirect article "America, the Beautiful" to "America the Beautiful"', async function () {
const dirEntry = await localZimArchive.getDirEntryByPath('A/America,_the_Beautiful.html');
expect(dirEntry).to.not.be.null;
expect(dirEntry.isRedirect()).to.be.true;
expect(dirEntry.getTitleOrUrl()).to.equal('America, the Beautiful');
const resolvedDirEntry = await new Promise(resolve => {
localZimArchive.resolveRedirect(dirEntry, resolve);
});
expect(resolvedDirEntry).to.not.be.null;
expect(resolvedDirEntry.isRedirect()).to.be.false;
expect(resolvedDirEntry.getTitleOrUrl()).to.equal('America the Beautiful');
});
it('Load image "m/RayCharles_AManAndHisSoul.jpg"', async function () {
const dirEntry = await localZimArchive.getDirEntryByPath('I/m/RayCharles_AManAndHisSoul.jpg');
expect(dirEntry).to.not.be.null;
expect(dirEntry.namespace + '/' + dirEntry.url).to.equal('I/m/RayCharles_AManAndHisSoul.jpg');
expect(dirEntry.getMimetype()).to.equal('image/jpeg');
const data = await new Promise(resolve => {
localZimArchive.readBinaryFile(dirEntry, (_, data) => resolve(data));
});
expect(data.length).to.equal(4951);
const beginning = new Uint8Array([255, 216, 255, 224, 0, 16, 74, 70, 73, 70, 0, 1, 1, 0, 0, 1]);
expect(data.slice(0, beginning.length).toString()).to.equal(beginning.toString());
});
it('Load stylesheet "-/s/style.css"', async function () {
const dirEntry = await localZimArchive.getDirEntryByPath('-/s/style.css');
expect(dirEntry).to.not.be.null;
expect(dirEntry.namespace + '/' + dirEntry.url).to.equal('-/s/style.css');
expect(dirEntry.getMimetype()).to.equal('text/css');
const data = await new Promise(resolve => {
localZimArchive.readBinaryFile(dirEntry, (_, data) => resolve(data));
});
expect(data.length).to.equal(104495);
const parsedData = utf8.parse(data);
const beginning = '\n/* start http://en.wikipedia.org/w/load.php?debug=false&lang=en&modules=site&only=styles&skin=vector';
expect(parsedData.slice(0, beginning.length)).to.equal(beginning);
});
it('Load javascript "-/j/local.js"', async function () {
const dirEntry = await localZimArchive.getDirEntryByPath('-/j/local.js');
expect(dirEntry).to.not.be.null;
expect(dirEntry.namespace + '/' + dirEntry.url).to.equal('-/j/local.js');
expect(dirEntry.getMimetype()).to.equal('application/javascript');
const data = await new Promise(resolve => {
localZimArchive.readBinaryFile(dirEntry, (_, data) => resolve(data));
});
expect(data.length).to.equal(41);
const parsedData = utf8.parse(data);
const beginning = 'console.log( "mw.loader';
expect(parsedData.slice(0, beginning.length)).to.equal(beginning);
});
it('Load split article "A/Ray_Charles.html"', async function () {
const dirEntry = await localZimArchive.getDirEntryByPath('A/Ray_Charles.html');
expect(dirEntry).to.not.be.null;
expect(dirEntry.namespace + '/' + dirEntry.url).to.equal('A/Ray_Charles.html');
expect(dirEntry.getMimetype()).to.equal('text/html');
const article = await new Promise(resolve => {
localZimArchive.readUtf8File(dirEntry, (_, article) => resolve(article));
});
expect(dirEntry.getTitleOrUrl()).to.equal('Ray Charles');
expect(article.length).to.equal(157186);
expect(article.indexOf('the only true genius in show business')).to.equal(5535);
expect(article.indexOf('Random Access Memories')).to.equal(154107);
});
});
describe('Zim random and main articles', function () {
it('Find a random article', async function () {
const dirEntry = await new Promise(resolve => {
localZimArchive.getRandomDirEntry(resolve);
});
expect(dirEntry).to.not.be.null;
expect(dirEntry.getTitleOrUrl()).to.not.be.null;
});
it('Find the main article', async function () {
const dirEntry = await new Promise(resolve => {
localZimArchive.getMainPageDirEntry(resolve);
});
expect(dirEntry).to.not.be.null;
expect(dirEntry.getTitleOrUrl()).to.equal('Summary');
});
});

View File

@ -1,398 +0,0 @@
/**
* tests.js : Unit tests implemented with qunit
*
* Copyright 2013-2014 Mossroy and contributors
* License GPL v3:
*
* This file is part of Kiwix.
*
* Kiwix is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Kiwix is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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/>
*/
/* global QUnit, Promise */
// import '../www/js/lib/promisePolyfill.js';
// import '../www/js/lib/arrayFromPolyfill.js';
import '../js/init.js';
// import '../www/js/app.js';
import zimArchive from '../../../www/js/lib/zimArchive.js';
import zimDirEntry from '../../../www/js/lib/zimDirEntry.js';
import util from '../../../www/js/lib/util.js';
import uiUtil from '../../../www/js/lib/uiUtil.js';
import utf8 from '../../../www/js/lib/utf8.js';
var localZimArchive;
/**
* Make an HTTP request for a Blob and return a Promise
*
* @param {String} url URL to download from
* @param {String} name Name to give to the Blob instance
* @returns {Promise<Blob>} A Promise for the Blob
*/
function makeBlobRequest (url, name) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE) {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 0) {
var blob = new Blob([xhr.response], { type: 'application/octet-stream' });
blob.name = name;
resolve(blob);
} else {
console.error('Error reading file ' + url + ' status:' + xhr.status + ', statusText:' + xhr.statusText);
reject(new Error({
status: xhr.status,
statusText: xhr.statusText
}));
}
}
};
xhr.onerror = function () {
console.error('Error reading file ' + url + ' status:' + xhr.status + ', statusText:' + xhr.statusText);
reject(new Error({
status: xhr.status,
statusText: xhr.statusText
}));
};
xhr.responseType = 'blob';
xhr.send();
});
}
// Let's try to download the ZIM files
var zimArchiveFiles = [];
var splitBlobs = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o'].map(function (c) {
var filename = 'wikipedia_en_ray_charles_2015-06.zima' + c;
return makeBlobRequest('tests/zims/legacy-ray-charles/' + filename, filename);
});
Promise.all(splitBlobs)
.then(function (values) {
zimArchiveFiles = values;
}).then(function () {
// Create a localZimArchive from selected files, in order to run the following tests
localZimArchive = new zimArchive.ZIMArchive(zimArchiveFiles, null, function (zimArchive) {
runTests();
});
});
var runTests = function () {
QUnit.module('environment');
QUnit.test('qunit test', function (assert) {
assert.equal('test', 'test', 'QUnit is properly configured');
});
QUnit.test('check archive files are read', function (assert) {
assert.ok(zimArchiveFiles && zimArchiveFiles[0] && zimArchiveFiles[0].size > 0, 'ZIM file read and not empty');
});
QUnit.module('utils');
QUnit.test('check reading an IEEE_754 float from 4 bytes', function (assert) {
var byteArray = new Uint8Array(4);
// This example is taken from https://fr.wikipedia.org/wiki/IEEE_754#Un_exemple_plus_complexe
// 1100 0010 1110 1101 0100 0000 0000 0000
byteArray[0] = 194;
byteArray[1] = 237;
byteArray[2] = 64;
byteArray[3] = 0;
var float = util.readFloatFrom4Bytes(byteArray, 0);
assert.equal(float, -118.625, 'the IEEE_754 float should be converted as -118.625');
});
QUnit.test('check upper/lower case variations', function (assert) {
var testString1 = 'téléphone';
var testString2 = 'Paris';
var testString3 = 'le Couvre-chef Est sur le porte-manteaux';
var testString4 = 'épée';
var testString5 = '$¥€“«xριστός» †¡Ἀνέστη!”';
var testString6 = 'Καλά Νερά Μαγνησία žižek';
assert.equal(util.allCaseFirstLetters(testString1).indexOf('Téléphone') >= 0, true, 'The first letter should be uppercase');
assert.equal(util.allCaseFirstLetters(testString2).indexOf('paris') >= 0, true, 'The first letter should be lowercase');
assert.equal(util.allCaseFirstLetters(testString3).indexOf('Le Couvre-Chef Est Sur Le Porte-Manteaux') >= 0, true, 'The first letter of every word should be uppercase');
assert.equal(util.allCaseFirstLetters(testString4).indexOf('Épée') >= 0, true, 'The first letter should be uppercase (with accent)');
assert.equal(util.allCaseFirstLetters(testString5).indexOf('$¥€“«Xριστός» †¡ἀνέστη!”') >= 0, true, 'First non-punctuation/non-currency Unicode letter should be uppercase, second (with breath mark) lowercase');
assert.equal(util.allCaseFirstLetters(testString6, 'full').indexOf('ΚΑΛΆ ΝΕΡΆ ΜΑΓΝΗΣΊΑ ŽIŽEK') >= 0, true, 'All Unicode letters should be uppercase');
});
QUnit.test('check removal of parameters in URL', function (assert) {
var baseUrl = "A/Che cosa è l'amore?.html";
var testUrls = [
"A/Che%20cosa%20%C3%A8%20l'amore%3F.html?param1=toto&param2=titi",
"A/Che%20cosa%20%C3%A8%20l'amore%3F.html?param1=toto&param2=titi#anchor",
"A/Che%20cosa%20%C3%A8%20l'amore%3F.html#anchor"
];
testUrls.forEach(function (testUrl) {
assert.equal(decodeURIComponent(
uiUtil.removeUrlParameters(testUrl)
), baseUrl);
});
});
QUnit.module('ZIM initialisation');
QUnit.test('ZIM archive is ready', function (assert) {
assert.ok(localZimArchive.isReady() === true, 'ZIM archive should be set as ready');
});
QUnit.module('ZIM metadata');
QUnit.test('read ZIM language', function (assert) {
var done = assert.async();
assert.expect(1);
var callbackFunction = function (language) {
assert.equal(language, 'eng', 'The language read inside the Metadata should be "eng" for "English"');
done();
};
localZimArchive.getMetadata('Language', callbackFunction);
});
QUnit.test('try to read a missing metadata', function (assert) {
var done = assert.async();
assert.expect(1);
var callbackFunction = function (string) {
assert.equal(string, undefined, 'The metadata zzz should not be found inside the ZIM');
done();
};
localZimArchive.getMetadata('zzz', callbackFunction);
});
QUnit.module('zim_direntry_search_and_read');
QUnit.test("check DirEntry.fromStringId 'A Fool for You'", function (assert) {
var done = assert.async();
var aFoolForYouDirEntry = zimDirEntry.DirEntry.fromStringId(localZimArchive.file, '5856|7|A|0|2|A_Fool_for_You.html|A Fool for You|false|undefined');
assert.expect(2);
var callbackFunction = function (dirEntry, htmlArticle) {
assert.ok(htmlArticle && htmlArticle.length > 0, 'Article not empty');
// Remove new lines
htmlArticle = htmlArticle.replace(/[\r\n]/g, ' ');
assert.ok(htmlArticle.match('^.*<h1[^>]*>A Fool for You</h1>'), "'A Fool for You' title somewhere in the article");
done();
};
localZimArchive.readUtf8File(aFoolForYouDirEntry, callbackFunction);
});
QUnit.test("check findDirEntriesWithPrefix 'A'", function (assert) {
var done = assert.async();
assert.expect(2);
var callbackFunction = function (dirEntryList) {
assert.ok(dirEntryList && dirEntryList.length === 5, 'Article list with 5 results');
var firstDirEntry = dirEntryList[0];
assert.equal(firstDirEntry.getTitleOrUrl(), 'A Fool for You', 'First result should be "A Fool for You"');
done();
};
localZimArchive.findDirEntriesWithPrefix({ prefix: 'A', size: 5 }, callbackFunction, true);
});
QUnit.test("check findDirEntriesWithPrefix 'a'", function (assert) {
var done = assert.async();
assert.expect(2);
var callbackFunction = function (dirEntryList) {
assert.ok(dirEntryList && dirEntryList.length === 5, 'Article list with 5 results');
var firstDirEntry = dirEntryList[0];
assert.equal(firstDirEntry.getTitleOrUrl(), 'A Fool for You', 'First result should be "A Fool for You"');
done();
};
localZimArchive.findDirEntriesWithPrefix({ prefix: 'a', size: 5 }, callbackFunction, true);
});
QUnit.test("check findDirEntriesWithPrefix 'blues brothers'", function (assert) {
var done = assert.async();
assert.expect(2);
var callbackFunction = function (dirEntryList) {
assert.ok(dirEntryList && dirEntryList.length === 3, 'Article list with 3 result');
var firstDirEntry = dirEntryList[0];
assert.equal(firstDirEntry.getTitleOrUrl(), 'Blues Brothers (film)', 'First result should be "Blues Brothers (film)"');
done();
};
localZimArchive.findDirEntriesWithPrefix({ prefix: 'blues brothers', size: 5 }, callbackFunction, true);
});
QUnit.test("article '(The Night Time Is) The Right Time' correctly redirects to 'Night Time Is the Right Time'", function (assert) {
var done = assert.async();
assert.expect(6);
localZimArchive.getDirEntryByPath('A/(The_Night_Time_Is)_The_Right_Time.html').then(function (dirEntry) {
assert.ok(dirEntry !== null, 'DirEntry found');
if (dirEntry !== null) {
assert.ok(dirEntry.isRedirect(), 'DirEntry is a redirect.');
assert.equal(dirEntry.getTitleOrUrl(), '(The Night Time Is) The Right Time', 'Correct redirect title name.');
localZimArchive.resolveRedirect(dirEntry, function (dirEntry) {
assert.ok(dirEntry !== null, 'DirEntry found');
assert.ok(!dirEntry.isRedirect(), 'DirEntry is not a redirect.');
assert.equal(dirEntry.getTitleOrUrl(), 'Night Time Is the Right Time', 'Correct redirected title name.');
done();
});
} else {
done();
}
});
});
QUnit.test("article 'Raelettes' correctly redirects to 'The Raelettes'", function (assert) {
var done = assert.async();
assert.expect(6);
localZimArchive.getDirEntryByPath('A/Raelettes.html').then(function (dirEntry) {
assert.ok(dirEntry !== null, 'DirEntry found');
if (dirEntry !== null) {
assert.ok(dirEntry.isRedirect(), 'DirEntry is a redirect.');
assert.equal(dirEntry.getTitleOrUrl(), 'Raelettes', 'Correct redirect title name.');
localZimArchive.resolveRedirect(dirEntry, function (dirEntry) {
assert.ok(dirEntry !== null, 'DirEntry found');
assert.ok(!dirEntry.isRedirect(), 'DirEntry is not a redirect.');
assert.equal(dirEntry.getTitleOrUrl(), 'The Raelettes', 'Correct redirected title name.');
done();
});
} else {
done();
}
});
});
QUnit.test("article 'Bein Green' correctly redirects to 'Bein' Green", function (assert) {
var done = assert.async();
assert.expect(6);
localZimArchive.getDirEntryByPath('A/Bein_Green.html').then(function (dirEntry) {
assert.ok(dirEntry !== null, 'DirEntry found');
if (dirEntry !== null) {
assert.ok(dirEntry.isRedirect(), 'DirEntry is a redirect.');
assert.equal(dirEntry.getTitleOrUrl(), 'Bein Green', 'Correct redirect title name.');
localZimArchive.resolveRedirect(dirEntry, function (dirEntry) {
assert.ok(dirEntry !== null, 'DirEntry found');
assert.ok(!dirEntry.isRedirect(), 'DirEntry is not a redirect.');
assert.equal(dirEntry.getTitleOrUrl(), "Bein' Green", 'Correct redirected title name.');
done();
});
} else {
done();
}
});
});
QUnit.test("article 'America, the Beautiful' correctly redirects to 'America the Beautiful'", function (assert) {
var done = assert.async();
assert.expect(6);
localZimArchive.getDirEntryByPath('A/America,_the_Beautiful.html').then(function (dirEntry) {
assert.ok(dirEntry !== null, 'DirEntry found');
if (dirEntry !== null) {
assert.ok(dirEntry.isRedirect(), 'DirEntry is a redirect.');
assert.equal(dirEntry.getTitleOrUrl(), 'America, the Beautiful', 'Correct redirect title name.');
localZimArchive.resolveRedirect(dirEntry, function (dirEntry) {
assert.ok(dirEntry !== null, 'DirEntry found');
assert.ok(!dirEntry.isRedirect(), 'DirEntry is not a redirect.');
assert.equal(dirEntry.getTitleOrUrl(), 'America the Beautiful', 'Correct redirected title name.');
done();
});
} else {
done();
}
});
});
QUnit.test("Image 'm/RayCharles_AManAndHisSoul.jpg' can be loaded", function (assert) {
var done = assert.async();
assert.expect(5);
localZimArchive.getDirEntryByPath('I/m/RayCharles_AManAndHisSoul.jpg').then(function (dirEntry) {
assert.ok(dirEntry !== null, 'DirEntry found');
if (dirEntry !== null) {
assert.equal(dirEntry.namespace + '/' + dirEntry.url, 'I/m/RayCharles_AManAndHisSoul.jpg', 'URL is correct.');
assert.equal(dirEntry.getMimetype(), 'image/jpeg', 'MIME type is correct.');
localZimArchive.readBinaryFile(dirEntry, function (title, data) {
assert.equal(data.length, 4951, 'Data length is correct.');
var beginning = new Uint8Array([255, 216, 255, 224, 0, 16, 74, 70,
73, 70, 0, 1, 1, 0, 0, 1]);
assert.equal(data.slice(0, beginning.length).toString(), beginning.toString(), 'Data beginning is correct.');
done();
});
} else {
done();
}
});
});
QUnit.test("Stylesheet '-/s/style.css' can be loaded", function (assert) {
var done = assert.async();
assert.expect(5);
localZimArchive.getDirEntryByPath('-/s/style.css').then(function (dirEntry) {
assert.ok(dirEntry !== null, 'DirEntry found');
if (dirEntry !== null) {
assert.equal(dirEntry.namespace + '/' + dirEntry.url, '-/s/style.css', 'URL is correct.');
assert.equal(dirEntry.getMimetype(), 'text/css', 'MIME type is correct.');
localZimArchive.readBinaryFile(dirEntry, function (dirEntry, data) {
assert.equal(data.length, 104495, 'Data length is correct.');
data = utf8.parse(data);
var beginning = '\n/* start http://en.wikipedia.org/w/load.php?debug=false&lang=en&modules=site&only=styles&skin=vector';
assert.equal(data.slice(0, beginning.length), beginning, 'Content starts correctly.');
done();
});
} else {
done();
}
});
});
QUnit.test("Javascript '-/j/local.js' can be loaded", function (assert) {
var done = assert.async();
assert.expect(5);
localZimArchive.getDirEntryByPath('-/j/local.js').then(function (dirEntry) {
assert.ok(dirEntry !== null, 'DirEntry found');
if (dirEntry !== null) {
assert.equal(dirEntry.namespace + '/' + dirEntry.url, '-/j/local.js', 'URL is correct.');
assert.equal(dirEntry.getMimetype(), 'application/javascript', 'MIME type is correct.');
localZimArchive.readBinaryFile(dirEntry, function (dirEntry, data) {
assert.equal(data.length, 41, 'Data length is correct.');
data = utf8.parse(data);
var beginning = 'console.log( "mw.loader';
assert.equal(data.slice(0, beginning.length), beginning, 'Content starts correctly.');
done();
});
} else {
done();
}
});
});
QUnit.test("Split article 'A/Ray_Charles.html' can be loaded", function (assert) {
var done = assert.async();
assert.expect(7);
localZimArchive.getDirEntryByPath('A/Ray_Charles.html').then(function (dirEntry) {
assert.ok(dirEntry !== null, 'Title found');
if (dirEntry !== null) {
assert.equal(dirEntry.namespace + '/' + dirEntry.url, 'A/Ray_Charles.html', 'URL is correct.');
assert.equal(dirEntry.getMimetype(), 'text/html', 'MIME type is correct.');
localZimArchive.readUtf8File(dirEntry, function (dirEntry2, data) {
assert.equal(dirEntry2.getTitleOrUrl(), 'Ray Charles', 'Title is correct.');
assert.equal(data.length, 157186, 'Data length is correct.');
assert.equal(data.indexOf('the only true genius in show business'), 5535, 'Specific substring at beginning found.');
assert.equal(data.indexOf('Random Access Memories'), 154107, 'Specific substring at end found.');
done();
});
} else {
done();
}
});
});
QUnit.module('zim_random_and_main_article');
QUnit.test('check that a random article is found', function (assert) {
var done = assert.async();
assert.expect(2);
var callbackRandomArticleFound = function (dirEntry) {
assert.ok(dirEntry !== null, 'One DirEntry should be found');
assert.ok(dirEntry.getTitleOrUrl() !== null, 'The random DirEntry should have a title');
done();
};
localZimArchive.getRandomDirEntry(callbackRandomArticleFound);
});
QUnit.test('check that the main article is found', function (assert) {
var done = assert.async();
assert.expect(2);
var callbackMainPageArticleFound = function (dirEntry) {
assert.ok(dirEntry !== null, 'Main DirEntry should be found');
assert.equal(dirEntry.getTitleOrUrl(), 'Summary', 'The main DirEntry should be called Summary');
done();
};
localZimArchive.getMainPageDirEntry(callbackMainPageArticleFound);
});
QUnit.start();
};

View File

@ -31,7 +31,7 @@ import XZWASM from './xzdec-wasm.js';
var XZMachineType = null; var XZMachineType = null;
// Select asm or wasm conditionally // Select asm or wasm conditionally
if ('WebAssembly' in self) { if ('WebAssembly' in self && 'Fetch' in self) {
console.debug('Instantiating WASM xz decoder'); console.debug('Instantiating WASM xz decoder');
XZMachineType = 'WASM'; XZMachineType = 'WASM';
} else { } else {

View File

@ -95,7 +95,7 @@ var instantiateDecoder = function (instance) {
}; };
// Select asm or wasm conditionally // Select asm or wasm conditionally
if ('WebAssembly' in self) { if ('WebAssembly' in self && 'Fetch' in self) {
console.debug('Instantiating WASM zstandard decoder'); console.debug('Instantiating WASM zstandard decoder');
ZSTDMachineType = 'WASM'; ZSTDMachineType = 'WASM';
} else { } else {