Tests for admin search, simplifications

v1.18.x
Peter Jaszkowiak 8 years ago
parent e3dd68e19c
commit de6ced4e07

@ -83,5 +83,7 @@
"white" : false, // true: Check against strict whitespace and indentation rules "white" : false, // true: Check against strict whitespace and indentation rules
// Custom Globals // Custom Globals
"globals" : {} // additional predefined global variables "globals" : {
"Promise": true
} // additional predefined global variables
} }

@ -339,6 +339,68 @@
Translator.moduleFactories = {}; Translator.moduleFactories = {};
/**
* Remove the translator patterns from text
* @param {string} text
* @returns {string}
*/
Translator.removePatterns = function removePatterns(text) {
var len = text.length;
var cursor = 0;
var lastBreak = 0;
var level = 0;
var out = '';
var sub;
while (cursor < len) {
sub = text.slice(cursor, cursor + 2);
if (sub === '[[') {
if (level === 0) {
out += text.slice(lastBreak, cursor);
}
level += 1;
cursor += 2;
} else if (sub === ']]') {
level -= 1;
cursor += 2;
if (level === 0) {
lastBreak = cursor;
}
} else {
cursor += 1;
}
}
out += text.slice(lastBreak, cursor);
return out;
};
/**
* Escape translator patterns in text
* @param {string} text
* @returns {string}
*/
Translator.escape = function escape(text) {
return typeof text === 'string' ? text.replace(/\[\[([\S]*?)\]\]/g, '\\[\\[$1\\]\\]') : text;
};
/**
* Unescape escaped translator patterns in text
* @param {string} text
* @returns {string}
*/
Translator.unescape = function unescape(text) {
return typeof text === 'string' ? text.replace(/\\\[\\\[([\S]*?)\\\]\\\]/g, '[[$1]]') : text;
};
/**
* Construct a translator pattern
*/
Translator.compile = function compile() {
var args = Array.prototype.slice.call(arguments, 0);
return '[[' + args.join(', ') + ']]';
};
return Translator; return Translator;
}()); }());
@ -348,12 +410,16 @@
*/ */
Translator: Translator, Translator: Translator,
compile: Translator.compile,
escape: Translator.escape,
unescape: Translator.unescape,
getLanguage: Translator.getLanguage,
/** /**
* Legacy translator function for backwards compatibility * Legacy translator function for backwards compatibility
*/ */
translate: function translate(text, language, callback) { translate: function translate(text, language, callback) {
// console.warn('[translator] `translator.translate(text, [lang, ]callback)` is deprecated. ' + // TODO: deprecate?
// 'Use the `translator.Translator` class instead.');
var cb = callback; var cb = callback;
var lang = language; var lang = language;
@ -373,31 +439,6 @@
}); });
}, },
/**
* Construct a translator pattern
* @param {string} name - Translation name
* @param {string[]} args - Optional arguments for the pattern
*/
compile: function compile() {
var args = Array.prototype.slice.call(arguments, 0);
return '[[' + args.join(', ') + ']]';
},
/**
* Escape translation patterns from text
*/
escape: function escape(text) {
return typeof text === 'string' ? text.replace(/\[\[([\S]*?)\]\]/g, '\\[\\[$1\\]\\]') : text;
},
/**
* Unescape translation patterns from text
*/
unescape: function unescape(text) {
return typeof text === 'string' ? text.replace(/\\\[\\\[([\S]*?)\\\]\\\]/g, '[[$1]]') : text;
},
/** /**
* Add translations to the cache * Add translations to the cache
*/ */
@ -422,11 +463,6 @@
adaptor.getTranslations(language, namespace, callback); adaptor.getTranslations(language, namespace, callback);
}, },
/**
* Get the language of the current environment, falling back to defaults
*/
getLanguage: Translator.getLanguage,
toggleTimeagoShorthand: function toggleTimeagoShorthand() { toggleTimeagoShorthand: function toggleTimeagoShorthand() {
var tmp = assign({}, jQuery.timeago.settings.strings); var tmp = assign({}, jQuery.timeago.settings.strings);
jQuery.timeago.settings.strings = assign({}, adaptor.timeagoShort); jQuery.timeago.settings.strings = assign({}, adaptor.timeagoShort);

@ -2,12 +2,11 @@
var fs = require('fs'); var fs = require('fs');
var path = require('path'); var path = require('path');
var nconf = require('nconf'); var sanitizeHTML = require('sanitize-html');
var sanitize = require('sanitize-html');
var languages = require('../languages'); var languages = require('../languages');
var meta = require('../meta');
var utils = require('../../public/src/utils'); var utils = require('../../public/src/utils');
var Translator = require('../../public/src/modules/translator');
function walk(directory) { function walk(directory) {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
@ -45,117 +44,102 @@ function loadLanguage(language, filename) {
}); });
} }
function filterDirectories(directories) {
return directories.map(function (dir) {
// get the relative path
return dir.replace(/^.*(admin.*?).tpl$/, '$1');
}).filter(function (dir) {
// exclude partials
// only include subpaths
return !dir.includes('/partials/') && /\/.*\//.test(dir);
});
}
function getAdminNamespaces() { function getAdminNamespaces() {
return walk(path.resolve('./public/templates/admin')) return walk(path.resolve('./public/templates/admin'))
.then(function (directories) { .then(filterDirectories);
return directories.map(function (dir) { }
return dir.replace(/^.*(admin.*?).tpl$/, '$1');
}).filter(function (dir) { function sanitize(html) {
return !dir.includes('/partials/'); // reduce the template to just meaningful text
}).filter(function (dir) { // remove all tags and strip out scripts, etc completely
return dir.match(/\/.*\//); return sanitizeHTML(html, {
}); allowedTags: [],
}); allowedAttributes: [],
});
}
function simplify(translations) {
return translations
// remove all mustaches
.replace(/(?:\{{1,2}[^\}]*?\}{1,2})/g, '')
// collapse whitespace
.replace(/(?:[ \t]*[\n\r]+[ \t]*)+/g, '\n')
.replace(/[\t ]+/g, ' ');
} }
var fallbackCache = {}; var fallbackCache = {};
function removeTranslatorPatterns(str) { function initFallback(namespace) {
var len = str.len; return readFile(path.resolve('./public/templates/', namespace + '.tpl'))
var cursor = 0; .then(function (template) {
var lastBreak = 0; var translations = sanitize(template);
var level = 0; translations = simplify(translations);
var out = ''; translations = Translator.removePatterns(translations);
var sub;
return {
while (cursor < len) { namespace: namespace,
sub = str.slice(cursor, cursor + 2); translations: translations,
if (sub === '[[') { };
if (level === 0) { });
out += str.slice(lastBreak, cursor);
}
level += 1;
cursor += 2;
} else if (sub === ']]') {
level -= 1;
cursor += 2;
if (level === 0) {
lastBreak = cursor;
}
} else {
cursor += 1;
}
}
out += str.slice(lastBreak, cursor);
return out;
} }
function fallback(namespace) { function fallback(namespace) {
fallbackCache[namespace] = fallbackCache[namespace] || // use cache if exists, else make it
readFile(path.resolve('./public/templates/', namespace + '.tpl')) fallbackCache[namespace] = fallbackCache[namespace] || initFallback(namespace);
.then(function (template) {
// reduce the template to just meaningful text
// remove scripts, etc and replace all tags with divs
var translations = sanitize(template, {
transformTags: {
'*': function () {
return {
tagName: 'div'
};
}
}
})
// remove all html tags, templating stuff, and translation strings
.replace(/(?:<div>)|(?:<\/div>)|(?:\{[^\{\}]*\})/g, '')
// collapse whitespace
.replace(/([\n\r]+ ?)+/g, '\n')
.replace(/[\t ]+/g, ' ');
translations = removeTranslatorPatterns(translations);
return {
namespace: namespace,
translations: translations,
};
});
return fallbackCache[namespace]; return fallbackCache[namespace];
} }
function initDict(language) { function initDict(language) {
return getAdminNamespaces().then(function (namespaces) { return getAdminNamespaces().then(function (namespaces) {
return Promise.all(namespaces.map(function (namespace) { return Promise.all(namespaces.map(function (namespace) {
return loadLanguage(language, namespace).then(function (translations) { return loadLanguage(language, namespace)
return { namespace: namespace, translations: translations }; .then(function (translations) {
}).then(function (params) { // join all translations into one string separated by newlines
var namespace = params.namespace; var str = Object.keys(translations).map(function (key) {
var translations = params.translations; return translations[key];
}).join('\n');
var str = Object.keys(translations).map(function (key) {
return translations[key]; return {
}).join('\n'); namespace: namespace,
translations: str,
return { };
namespace: namespace, })
translations: str // TODO: Use translator to get title for admin route?
}; .catch(function () {
}) // no translations for this route, fallback to template
// TODO: Use translator to get title for admin route? return fallback(namespace);
.catch(function () { })
return fallback(namespace); .catch(function () {
}) // no fallback, just return blank
.catch(function () { return {
return { namespace: namespace, translations: '' }; namespace: namespace,
}); translations: '',
};
});
})); }));
}); });
} }
var cache = {}; var cache = {};
function getDict(language, term) { function getDict(language) {
// use cache if exists, else make it
cache[language] = cache[language] || initDict(language); cache[language] = cache[language] || initDict(language);
return cache[language]; return cache[language];
} }
module.exports.getDict = getDict; module.exports.getDict = getDict;
module.exports.filterDirectories = filterDirectories;
module.exports.simplify = simplify;
module.exports.sanitize = sanitize;

@ -0,0 +1,82 @@
'use strict';
/*global require*/
var assert = require('assert');
var search = require('../src/admin/search.js');
describe('admin search', function () {
describe('filterDirectories', function () {
it('should resolve all paths to relative paths', function (done) {
assert.deepEqual(search.filterDirectories([
'hfjksfd/fdsgagag/admin/gdhgfsdg/sggag.tpl',
]), [
'admin/gdhgfsdg/sggag',
]);
done();
});
it('should exclude partials', function (done) {
assert.deepEqual(search.filterDirectories([
'hfjksfd/fdsgagag/admin/gdhgfsdg/sggag.tpl',
'dfahdfsgf/admin/partials/hgkfds/fdhsdfh.tpl',
]), [
'admin/gdhgfsdg/sggag',
]);
done();
});
it('should exclude files in the admin directory', function (done) {
assert.deepEqual(search.filterDirectories([
'hfjksfd/fdsgagag/admin/gdhgfsdg/sggag.tpl',
'dfdasg/admin/hjkdfsk.tpl',
]), [
'admin/gdhgfsdg/sggag',
]);
done();
});
});
describe('sanitize', function () {
it('should strip out scripts', function (done) {
assert.equal(
search.sanitize('Pellentesque tristique senectus' +
'<script>alert("nope");</script> habitant morbi'),
'Pellentesque tristique senectus' +
' habitant morbi'
);
done();
});
it('should remove all tags', function (done) {
assert.equal(
search.sanitize('<p>Pellentesque <b>habitant morbi</b> tristique senectus' +
'Aenean <i>vitae</i> est.Mauris <a href="placerat">eleifend</a> leo.</p>'),
'Pellentesque habitant morbi tristique senectus' +
'Aenean vitae est.Mauris eleifend leo.'
);
done();
});
});
describe('simplify', function () {
it('should remove all mustaches', function (done) {
assert.equal(
search.simplify(
'Pellentesque tristique {{senectus}}habitant morbi' +
'liquam tincidunt {{mauris.eu}}risus'
),
'Pellentesque tristique habitant morbi' +
'liquam tincidunt risus'
);
done();
});
it('should collapse all whitespace', function (done) {
assert.equal(
search.simplify(
'Pellentesque tristique habitant morbi' +
' \n\n liquam tincidunt mauris eu risus.'
),
'Pellentesque tristique habitant morbi' +
'\nliquam tincidunt mauris eu risus.'
);
done();
});
});
});

@ -233,3 +233,15 @@ describe('Translator modules', function () {
done(); done();
}); });
}); });
describe('Translator static methods', function () {
describe('.removePatterns', function () {
it('should remove translator patterns from text', function (done) {
assert.strictEqual(
Translator.removePatterns('Lorem ipsum dolor [[sit:amet]], consectetur adipiscing elit. [[sed:vitae, [[semper:dolor]]]] lorem'),
'Lorem ipsum dolor , consectetur adipiscing elit. lorem'
);
done();
});
});
});

Loading…
Cancel
Save