Publish Vulpes Preview 15 sources for desktop, Android and Tundra

Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

View file

@ -0,0 +1,437 @@
'use strict';
/* jshint node: true, mocha: true */
/* global suiteSetup */
var assert = require('chai').assert;
var path = require('path');
var fs = require('fs');
var helper = require('helper');
var AdmZip = require('adm-zip');
var jsdom = require('jsdom').jsdom;
function checkLayoutsJSON(zipPath, expectedArr) {
var zip = new AdmZip(zipPath);
var entry = zip.getEntry('js/settings/layouts.json');
var actualArr = JSON.parse(zip.readAsText(entry));
actualArr.forEach(function(layout) {
assert.isTrue(
!!layout.dictFileSize,
'Expect layout contain to non-zero dictFileSize, id=' + layout.id);
delete layout.dictFileSize;
});
expectedArr.forEach(function(layout) {
delete layout.dictFileSize;
});
assert.deepEqual(actualArr, expectedArr);
}
suite('Keyboard layouts building tests', function() {
suiteSetup(helper.cleanupWorkspace);
teardown(helper.cleanupWorkspace);
// default make -- build with selected layouts and dictionaries
test('APP=keyboard make', function(done) {
var cmd = 'APP=keyboard make';
helper.exec(cmd, function(error, stdout, stderr) {
helper.checkError(error, stdout, stderr);
var config = JSON.parse(process.env.BUILD_CONFIG);
var layoutIds = ['ar',
'bn-Avro',
'bn-Probhat',
'de',
'de-Neo',
'dz-BT',
'en',
'en-Colemak',
'en-Dvorak',
'en-Neo',
'eo',
'es',
'es-Americas',
'fa',
'fr',
'fr-CA',
'fr-CH',
'fr-Dvorak-bepo',
'he',
'hi',
'ig',
'ko',
'mk',
'my',
'pl',
'pt-BR',
'ta',
'te',
'th',
'uk',
'vi-Qwerty',
'vi-Telex',
'vi-Typewriter',
'wo',
'zh-Hans-Pinyin'];
var zipPath = path.join(process.cwd(), 'profile',
'webapps', 'keyboard.gaiamobile.org', 'application.zip');
var appDirPath = config.GAIA_DIR + '/apps/keyboard';
var layouts = layoutIds.map(function(layout) {
return 'js/layouts/' + layout + '.js';
});
// Unfortunately we can only whilelist files here because we shouldn't
// throw error if excluded files are missing (e.g. README).
var imes = [
'js/imes/jshangul/jshangul.js',
'js/imes/jspinyin/empinyin_files.data',
'js/imes/jspinyin/empinyin_files.js',
'js/imes/jspinyin/jspinyin.js',
'js/imes/jspinyin/libpinyin.js',
'js/imes/jspinyin/worker.js',
'js/imes/latin/latin.js',
'js/imes/latin/predictions.js',
'js/imes/latin/worker.js'
];
var dicts = [
'js/imes/latin/dictionaries/de.dict',
'js/imes/latin/dictionaries/en_us.dict',
'js/imes/latin/dictionaries/es.dict',
'js/imes/latin/dictionaries/fr.dict',
'js/imes/latin/dictionaries/pl.dict',
'js/imes/latin/dictionaries/pt_br.dict'
];
var checkList = [].concat(layouts, imes, dicts);
checkList.forEach(function(path) {
helper.checkFileInZip(zipPath, path, appDirPath + '/' + path);
});
// Verify inputs entry in manifest
var zip = new AdmZip(zipPath);
var entry = zip.getEntry('manifest.webapp');
var manifest = JSON.parse(zip.readAsText(entry));
var inputKeysInManifest = Object.keys(manifest.inputs);
assert.deepEqual(inputKeysInManifest.sort(),
[].concat(layoutIds, 'number', 'emoji').sort());
// Verify dictionary config
var dictJSON = JSON.parse(fs.readFileSync(
appDirPath +
'/test/build/integration/resources/' +
'default-make-layouts.json'));
checkLayoutsJSON(zipPath, dictJSON);
done();
});
});
// Build with all layouts and dictionaries
test('APP=keyboard GAIA_KEYBOARD_LAYOUTS=* make', function(done) {
var cmd = 'APP=keyboard GAIA_KEYBOARD_LAYOUTS=* make';
helper.exec(cmd, function(error, stdout, stderr) {
helper.checkError(error, stdout, stderr);
var config = JSON.parse(process.env.BUILD_CONFIG);
var zipPath = path.join(process.cwd(), 'profile',
'webapps', 'keyboard.gaiamobile.org', 'application.zip');
var appDirPath = config.GAIA_DIR + '/apps/keyboard';
var layoutIds =
fs.readdirSync(appDirPath + '/js/layouts').filter(function(filename) {
return (path.extname(filename) === '.js');
}).map(function(filename) {
return path.basename(filename, '.js');
}).filter(function(layoutId) {
// Exclude noIncludeInExpandLayoutIdSet layouts
return (['emoji'].indexOf(layoutId) === -1);
});
var layouts = layoutIds.map(function(layout) {
return 'js/layouts/' + layout + '.js';
});
// Unfortunately we can only whilelist files here because we shouldn't
// throw error if excluded files are missing (e.g. README).
// For the sake of simplicity let's only check the main scripts.
var imes = [
'js/imes/handwriting/handwriting.js',
'js/imes/india/india.js',
'js/imes/jshangul/jshangul.js',
'js/imes/jskanji/jskanji.js',
'js/imes/jspinyin/jspinyin.js',
'js/imes/latin/latin.js',
'js/imes/vietnamese/vietnamese.js',
'js/imes/myanmar/myanmar.js'
];
var dicts = [
'js/imes/latin/dictionaries/bg.dict',
'js/imes/latin/dictionaries/bs.dict',
'js/imes/latin/dictionaries/ca.dict',
'js/imes/latin/dictionaries/cs.dict',
'js/imes/latin/dictionaries/da.dict',
'js/imes/latin/dictionaries/de.dict',
'js/imes/latin/dictionaries/el.dict',
'js/imes/latin/dictionaries/en_gb.dict',
'js/imes/latin/dictionaries/en_us.dict',
'js/imes/latin/dictionaries/es.dict',
'js/imes/latin/dictionaries/eu.dict',
'js/imes/latin/dictionaries/fr.dict',
'js/imes/latin/dictionaries/ga.dict',
'js/imes/latin/dictionaries/gd.dict',
'js/imes/latin/dictionaries/gl.dict',
'js/imes/latin/dictionaries/gv.dict',
'js/imes/latin/dictionaries/hr.dict',
'js/imes/latin/dictionaries/hu.dict',
'js/imes/latin/dictionaries/it.dict',
'js/imes/latin/dictionaries/lt.dict',
'js/imes/latin/dictionaries/lv.dict',
'js/imes/latin/dictionaries/nb.dict',
'js/imes/latin/dictionaries/nl.dict',
'js/imes/latin/dictionaries/pl.dict',
'js/imes/latin/dictionaries/pt_br.dict',
'js/imes/latin/dictionaries/pt_pt.dict',
'js/imes/latin/dictionaries/ro.dict',
'js/imes/latin/dictionaries/ru.dict',
'js/imes/latin/dictionaries/sk.dict',
'js/imes/latin/dictionaries/sq.dict',
'js/imes/latin/dictionaries/sr-Cyrl.dict',
'js/imes/latin/dictionaries/sr-Latn.dict',
'js/imes/latin/dictionaries/sv.dict',
'js/imes/latin/dictionaries/tr.dict',
'js/imes/latin/dictionaries/af.dict'
];
var checkList = [].concat(layouts, imes, dicts);
checkList.forEach(function(path) {
helper.checkFileInZip(zipPath, path, appDirPath + '/' + path);
});
// Verify inputs entry in manifest
var zip = new AdmZip(zipPath);
var entry = zip.getEntry('manifest.webapp');
var manifest = JSON.parse(zip.readAsText(entry));
var inputKeysInManifest = Object.keys(manifest.inputs);
assert.deepEqual(inputKeysInManifest.sort(),
[].concat(layoutIds, 'number', 'emoji').sort());
// Verify dictionary config
var dictJSON = JSON.parse(fs.readFileSync(
appDirPath +
'/test/build/integration/resources/' +
'all-layout-make-layouts.json'));
checkLayoutsJSON(zipPath, dictJSON);
done();
});
});
// Build with all layouts with no dictionaries
// (preload ko layout here to avoid empty check.)
test('APP=keyboard GAIA_KEYBOARD_LAYOUTS=ko ' +
'GAIA_KEYBOARD_DOWNLOADABLE_LAYOUTS=noPreloadDictRequired make',
function(done) {
var cmd = 'APP=keyboard GAIA_KEYBOARD_LAYOUTS=ko ' +
'GAIA_KEYBOARD_DOWNLOADABLE_LAYOUTS=noPreloadDictRequired make';
helper.exec(cmd, function(error, stdout, stderr) {
helper.checkError(error, stdout, stderr);
var config = JSON.parse(process.env.BUILD_CONFIG);
var zipPath = path.join(process.cwd(), 'profile',
'webapps', 'keyboard.gaiamobile.org', 'application.zip');
var appDirPath = config.GAIA_DIR + '/apps/keyboard';
// For this test, we verify there isn't any dictionary
// in the zip.
var zip = new AdmZip(zipPath);
var entries = zip.getEntries();
var imePath = 'js/imes';
var sizeLimit = (1 << 10) * 100; // 100K
entries.forEach(function(entry) {
if (entry.entryName.substr(0, imePath.length) !== imePath) {
return;
}
var fileSize = entry.getData().length;
assert.isTrue(fileSize < sizeLimit,
'IME file is larger than non-dictionary limit: ' + entry.entryName +
', size: ' + fileSize + ' bytes.');
});
// Verify dictionary config
var dictJSON = JSON.parse(fs.readFileSync(
appDirPath +
'/test/build/integration/resources/' +
'no-preload-dict-required-make-layouts.json'));
checkLayoutsJSON(zipPath, dictJSON);
done();
});
});
// Build default layouts with only en dictionary, and extra IMEs
test('APP=keyboard GAIA_KEYBOARD_LAYOUTS=en ' +
'GAIA_KEYBOARD_DOWNLOADABLE_LAYOUTS=' +
'en,pt-BR,es,de,fr,fr-CA,pl,ko,zh-Hans-Pinyin,en-Dvorak make',
function(done) {
var cmd = 'APP=keyboard GAIA_KEYBOARD_LAYOUTS=en ' +
'GAIA_KEYBOARD_DOWNLOADABLE_LAYOUTS=' +
'en,pt-BR,es,de,fr,fr-CA,pl,ko,zh-Hans-Pinyin,en-Dvorak make';
helper.exec(cmd, function(error, stdout, stderr) {
helper.checkError(error, stdout, stderr);
var config = JSON.parse(process.env.BUILD_CONFIG);
var zipPath = path.join(process.cwd(), 'profile',
'webapps', 'keyboard.gaiamobile.org', 'application.zip');
var appDirPath = config.GAIA_DIR + '/apps/keyboard';
var layoutIds = config.GAIA_KEYBOARD_LAYOUTS.split(',').sort()
.filter(function(layoutId) {
// Exclude noIncludeInExpandLayoutIdSet layouts
return (['emoji'].indexOf(layoutId) === -1);
});
var layouts = layoutIds.map(function(layout) {
return 'js/layouts/' + layout + '.js';
});
// Unfortunately we can only whilelist files here because we shouldn't
// throw error if excluded files are missing (e.g. README).
var imes = [
'js/imes/jshangul/jshangul.js',
'js/imes/jspinyin/empinyin_files.data',
'js/imes/jspinyin/empinyin_files.js',
'js/imes/jspinyin/jspinyin.js',
'js/imes/jspinyin/libpinyin.js',
'js/imes/jspinyin/worker.js',
'js/imes/latin/latin.js',
'js/imes/latin/predictions.js',
'js/imes/latin/worker.js'
];
var dicts = [
'js/imes/latin/dictionaries/es.dict',
'js/imes/latin/dictionaries/fr.dict',
'js/imes/latin/dictionaries/pl.dict',
'js/imes/latin/dictionaries/pt_br.dict'
];
var checkList = [].concat(layouts, imes);
checkList.forEach(function(path) {
helper.checkFileInZip(zipPath, path, appDirPath + '/' + path);
});
// Verify inputs entry in manifest
var zip = new AdmZip(zipPath);
var entry = zip.getEntry('manifest.webapp');
var manifest = JSON.parse(zip.readAsText(entry));
var inputKeysInManifest = Object.keys(manifest.inputs);
// Only layouts with dictionaries should be declaried.
// Noted that en-Dvorak use the same dictionary as en so it should also
// be declaried.
assert.deepEqual(inputKeysInManifest.sort(),
['en', 'en-Dvorak', 'ko', 'number', 'zh-Hans-Pinyin']);
// Verify dictionaries are not built (except en_us.dict)
dicts.forEach(function(dict) {
var entry = zip.getEntry(dict);
assert.equal(entry, null, 'Dictionary should not be built: ' + dict);
});
// Verify dictionary config
var dictJSON = JSON.parse(fs.readFileSync(
appDirPath +
'/test/build/integration/resources/' +
'default-make-en-dict-layouts.json'));
checkLayoutsJSON(zipPath, dictJSON);
done();
});
});
});
suite('Keyboard settings building tests', function() {
suiteSetup(helper.cleanupWorkspace);
teardown(helper.cleanupWorkspace);
// parse settings.html and return domDoc.
var getSettingsDomDoc = function() {
var zipPath = path.join(process.cwd(), 'profile',
'webapps', 'keyboard.gaiamobile.org', 'application.zip');
// Verify settings.html content in manifest
var zip = new AdmZip(zipPath);
var entry = zip.getEntry('settings.html');
return jsdom(zip.readAsText(entry));
};
// return an array of <scripts> tag in <head>
var getScriptsFromDomDoc = function(domDoc) {
// We don't have Array.from in our node version, so use an old way to
// convert HTMLCollections to array
return Array.prototype.slice.call(
domDoc.head.getElementsByTagName('script'));
};
suite('For handwriting', function() {
// return an array of <sections> in the general panel
var getSectionsFromGeneralPanel = function(domDoc) {
return Array.prototype.slice.call(
domDoc.querySelectorAll('#general-container > section'));
};
// default: there shouldn't be handwriting elements in resulting file
test('APP=keyboard make', function(done) {
var cmd = 'APP=keyboard make';
helper.exec(cmd, function(error, stdout, stderr) {
helper.checkError(error, stdout, stderr);
var settingsDOMDoc = getSettingsDomDoc();
assert.isTrue(getScriptsFromDomDoc(settingsDOMDoc).every(function(elem){
return elem.src !== 'js/settings/handwriting_settings_view.js';
}), 'No script should include handwriting_settings_view.js');
assert.isTrue(
getSectionsFromGeneralPanel(settingsDOMDoc).every(function(elem){
return elem.id !== 'handwriting-settings';
}), 'No section in general panel should include handwriting settings');
done();
});
});
test('GAIA_KEYBOARD_LAYOUTS=zh-Hans-Handwriting APP=keyboard make',
function(done) {
var cmd = 'GAIA_KEYBOARD_LAYOUTS=zh-Hans-Handwriting APP=keyboard make';
helper.exec(cmd, function(error, stdout, stderr) {
helper.checkError(error, stdout, stderr);
var settingsDOMDoc = getSettingsDomDoc();
assert.isTrue(getScriptsFromDomDoc(settingsDOMDoc).some(function(elem){
return elem.src === 'js/settings/handwriting_settings_view.js';
}), 'Some script should include handwriting_settings_view.js');
assert.isTrue(
getSectionsFromGeneralPanel(settingsDOMDoc).some(function(elem){
return elem.id === 'handwriting-settings';
}),
'Some section in general panel should include handwriting settings');
done();
});
});
});
});

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,146 @@
[
{
"id": "de",
"name": "Deutsch",
"imEngineId": "latin",
"types": [
"email",
"password",
"text",
"url"
],
"dictFileSize": 2344627,
"preloaded": false,
"dictFilename": "de.dict",
"dictFilePath": "dictionaries/de.dict"
},
{
"id": "en",
"name": "English",
"imEngineId": "latin",
"types": [
"email",
"password",
"text",
"url"
],
"dictFileSize": 1451390,
"preloaded": true,
"dictFilename": "en_us.dict",
"dictFilePath": "dictionaries/en_us.dict"
},
{
"id": "en-Dvorak",
"name": "Dvorak",
"imEngineId": "latin",
"types": [
"email",
"password",
"text",
"url"
],
"dictFileSize": 1451390,
"preloaded": true,
"dictFilename": "en_us.dict",
"dictFilePath": "dictionaries/en_us.dict"
},
{
"id": "es",
"name": "Español",
"imEngineId": "latin",
"types": [
"email",
"password",
"text",
"url"
],
"dictFileSize": 1564842,
"preloaded": false,
"dictFilename": "es.dict",
"dictFilePath": "dictionaries/es.dict"
},
{
"id": "fr",
"name": "Français",
"imEngineId": "latin",
"types": [
"email",
"password",
"text",
"url"
],
"dictFileSize": 1874745,
"preloaded": false,
"dictFilename": "fr.dict",
"dictFilePath": "dictionaries/fr.dict"
},
{
"id": "fr-CA",
"name": "Français (Canadien)",
"imEngineId": "latin",
"types": [
"email",
"password",
"text",
"url"
],
"dictFileSize": 1874745,
"preloaded": false,
"dictFilename": "fr.dict",
"dictFilePath": "dictionaries/fr.dict"
},
{
"id": "ko",
"name": "한국어",
"imEngineId": "jshangul",
"types": [
"email",
"text",
"url"
],
"dictFileSize": 9562,
"preloaded": true
},
{
"id": "pl",
"name": "Polski",
"imEngineId": "latin",
"types": [
"email",
"password",
"text",
"url"
],
"dictFileSize": 1644422,
"preloaded": false,
"dictFilename": "pl.dict",
"dictFilePath": "dictionaries/pl.dict"
},
{
"id": "pt-BR",
"name": "Português (do Brasil)",
"imEngineId": "latin",
"types": [
"email",
"password",
"text",
"url"
],
"dictFileSize": 1524964,
"preloaded": false,
"dictFilename": "pt_br.dict",
"dictFilePath": "dictionaries/pt_br.dict"
},
{
"id": "zh-Hans-Pinyin",
"name": "拼音",
"imEngineId": "jspinyin",
"types": [
"email",
"text",
"url"
],
"dictFileSize": 2139624,
"preloaded": true
}
]

File diff suppressed because it is too large Load diff